Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.javascript.jscomp.CodingConvention.Bind; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.TernaryValue; import java.util.regex.Pattern; /** * A peephole optimization that minimizes code by simplifying conditional * expressions, replacing IFs with HOOKs, replacing object constructors * with literals, and simplifying returns. * */ class PeepholeSubstituteAlternateSyntax extends AbstractPeepholeOptimization { private static final int AND_PRECEDENCE = NodeUtil.precedence(Token.AND); private static final int OR_PRECEDENCE = NodeUtil.precedence(Token.OR); private static final int NOT_PRECEDENCE = NodeUtil.precedence(Token.NOT); private final boolean late; private final int STRING_SPLIT_OVERHEAD = ".split('.')".length(); static final DiagnosticType INVALID_REGULAR_EXPRESSION_FLAGS = DiagnosticType.error( "JSC_INVALID_REGULAR_EXPRESSION_FLAGS", "Invalid flags to RegExp constructor: {0}"); static final Predicate<Node> DONT_TRAVERSE_FUNCTIONS_PREDICATE = new Predicate<Node>() { @Override public boolean apply(Node input) { return !input.isFunction(); } }; /** * @param late When late is false, this mean we are currently running before * most of the other optimizations. In this case we would avoid optimizations * that would make the code harder to analyze (such as using string splitting, * merging statements with commas, etc). When this is true, we would * do anything to minimize for size. */ PeepholeSubstituteAlternateSyntax(boolean late) { this.late = late; } /** * Tries apply our various peephole minimizations on the passed in node. */ @Override @SuppressWarnings("fallthrough") public Node optimizeSubtree(Node node) { switch(node.getType()) { case Token.RETURN: { Node result = tryRemoveRedundantExit(node); if (result != node) { return result; } result = try

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>ReplaceExitWithBreak(node); if (result != node) { return result; } return tryReduceReturn(node); } case Token.THROW: { Node result = tryRemoveRedundantExit(node); if (result != node) { return result; } return tryReplaceExitWithBreak(node); } // TODO(johnlenz): Maybe remove redundant BREAK and CONTINUE. Overlaps // with MinimizeExitPoints. case Token.NOT: tryMinimizeCondition(node.getFirstChild()); return tryMinimizeNot(node); case Token.IF: tryMinimizeCondition(node.getFirstChild()); return tryMinimizeIf(node); case Token.EXPR_RESULT: tryMinimizeCondition(node.getFirstChild()); return node; case Token.HOOK: tryMinimizeCondition(node.getFirstChild()); return node; case Token.WHILE: case Token.DO: tryMinimizeCondition(NodeUtil.getConditionExpression(node)); return node; case Token.FOR: if (!NodeUtil.isForIn(node)) { tryJoinForCondition(node); tryMinimizeCondition(NodeUtil.getConditionExpression(node)); } return node; case Token.TRUE: case Token.FALSE: return reduceTrueFalse(node); case Token.NEW: node = tryFoldStandardConstructors(node); if (!node.isCall()) { return node; } // Fall through on purpose because tryFoldStandardConstructors() may // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.BLOCK: return tryReplaceIf(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); default: return node; //Nothing changed } } private void tryJoinForCondition(Node n) { if (!late) { return; } Node block = n.getLastChild(); Node maybeIf = block.getFirstChild(); if (maybeIf != null && maybeIf.isIf()) { Node maybeBreak = maybeIf.getChildAtIndex(1).getFirstChild(); if (maybeBreak != null && maybeBreak.isBreak() && !maybeBreak.hasChildren()) { // Preserve the IF ELSE expression is there is one. if (maybeIf.getChildCount() == 3) { block.replaceChild(maybeIf, maybeIf.getLastChild().detachFromParent()); } else { block.removeFirstChild(); } Node ifCondition = maybeIf.removeFirstChild(); Node fixedIfCondition = IR.not(ifCondition) .srcref(ifCondition); // OK, join the IF expression with the FOR expression Node forCondition = NodeUtil.getConditionExpression(n); if (forCondition.isEmpty()) { n.replaceChild(forCondition, fixed

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>IfCondition); } else { Node replacement = new Node(Token.AND); n.replaceChild(forCondition, replacement); replacement.addChildToBack(forCondition); Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); Bind bind = getCodingConvention().describeFunctionBind(callTarget, false); if (bind != null) { // replace the call target bind.target.detachFromParent(); n.replaceChild(callTarget, bind.target); callTarget = bind.target; // push the parameters addParameterAfter(bind.parameters, callTarget); // add the this value before the parameters if necessary if (bind.thisValue != null && !NodeUtil.isUndefined(bind.thisValue)) { // rewrite from "fn(a, b)" to "fn.call(thisValue, a, b)" Node newCallTarget = IR.getprop( callTarget.cloneTree(), IR.string("call").srcref(callTarget)); n.replaceChild(callTarget, newCallTarget); n.addChildAfter(bind.thisValue.cloneTree(), newCallTarget); n.putBooleanProp(Node.FREE_CALL, false); } else { n.putBooleanProp(Node.FREE_CALL, true); } reportCodeChange(); } return n; } private void addParameterAfter(Node parameterList, Node after) { if (parameterList != null) { // push the last parameter to the head of the list first. addParameterAfter(parameterList.getNext(), after); after.getParent().addChildAfter(parameterList.cloneTree(), after); } } private Node trySplitComma(Node n) { if (late) { return n; } Node parent = n.getParent(); Node left = n.getFirstChild(); Node right = n.getLastChild(); if (parent.isExprResult() && !parent.getParent().isLabel()) { // split comma n.detachChildren(); // Replace the original expression with the left operand. parent.replaceChild(n, left); // Add the right expression afterward. Node newStatement = IR.exprResult(right); newStatement.copyInformationFrom(n); //This modifies outside the subtree, which is not //desirable in a peephole optimization. parent.getParent().addChildAfter(newStatement, parent); reportCodeChange(); return left; } else { return n; } } /** * Use "return x?1:2;" in place of "if(x)return 1;return 2;" */ private Node tryReplaceIf(Node n) { for (Node child = n.getFirstChild(); child != null; child = child.getNext()){ if (child.isIf()){ Node cond = child.getFirstChild(); Node thenBranch = cond.getNext(); Node elseBranch = thenBranch.getNext(); Node nextNode = child.getNext(); if (nextNode != null && elseBranch == null && isReturnBlock(thenBranch) && nextNode.isIf()) { Node nextCond = nextNode.getFirstChild(); Node nextThen = nextCond.getNext(); Node next

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>Else = nextThen.getNext(); if (thenBranch.isEquivalentToTyped(nextThen)) { // Transform // if (x) return 1; if (y) return 1; // to // if (x||y) return 1; child.detachFromParent(); child.detachChildren(); Node newCond = new Node(Token.OR, cond); nextNode.replaceChild(nextCond, newCond); newCond.addChildToBack(nextCond); reportCodeChange(); } else if (nextElse != null && thenBranch.isEquivalentToTyped(nextElse)) { // Transform // if (x) return 1; if (y) foo() else return 1; // to // if (!x&&y) foo() else return 1; child.detachFromParent(); child.detachChildren(); Node newCond = new Node(Token.AND, IR.not(cond).srcref(cond)); nextNode.replaceChild(nextCond, newCond); newCond.addChildToBack(nextCond); reportCodeChange(); } } else if (nextNode != null && elseBranch == null && isReturnBlock(thenBranch) && isReturnExpression(nextNode)) { Node thenExpr = null; // if(x)return; return 1 -> return x?void 0:1 if (isReturnExpressBlock(thenBranch)) { thenExpr = getBlockReturnExpression(thenBranch); thenExpr.detachFromParent(); } else { thenExpr = NodeUtil.newUndefinedNode(child); } Node elseExpr = nextNode.getFirstChild(); cond.detachFromParent(); elseExpr.detachFromParent(); Node returnNode = IR.returnNode( IR.hook(cond, thenExpr, elseExpr) .srcref(child)); n.replaceChild(child, returnNode); n.removeChild(nextNode); reportCodeChange(); } else if (elseBranch != null && statementMustExitParent(thenBranch)) { child.removeChild(elseBranch); n.addChildAfter(elseBranch, child); reportCodeChange(); } } } return n; } private boolean statementMustExitParent(Node n) { switch (n.getType()) { case Token.THROW: case Token.RETURN: return true; case Token.BLOCK: if (n.hasChildren()) { Node child = n.getLastChild(); return statementMustExitParent(child); } return false; // TODO(johnlenz): handle TRY/FINALLY case Token.FUNCTION: default: return false; } } /** * Use "void 0" in place of "undefined" */ private Node tryReplaceUndefined(Node n) { // TODO(johnlenz): consider doing this as a normalization. if (isASTNormalized() && NodeUtil.isUndefined(n) && !NodeUtil.isLValue(n)) { Node replacement = NodeUtil.newUndefinedNode(n); n.getParent().replaceChild(n, replacement); reportCodeChange(); return replacement; } return n; } /** * Reduce "return undefined" or "

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>, the node can be removed. * For example: * "if (a) {return f()} return f();" ==> "if (a) {} return f();" * "if (a) {throw 'ow'} throw 'ow';" ==> "if (a) {} throw 'ow';" * * @param n An follow control exit expression (a THROW or RETURN node) * @return The replacement for n, or the original if no change was made. */ private Node tryRemoveRedundantExit(Node n) { Node exitExpr = n.getFirstChild(); Node follow = ControlFlowAnalysis.computeFollowNode(n); // Skip pass all the finally blocks because both the fall through and return // will also trigger all the finally blocks. Node prefinallyFollows = follow; follow = skipFinallyNodes(follow); if (prefinallyFollows != follow) { // There were finally clauses if (!isPure(exitExpr)) { // Can't replace the return return n; } } if (follow == null && (n.isThrow() || exitExpr != null)) { // Can't complete remove a throw here or a return with a result. return n; } // When follow is null, this mean the follow of a break target is the // end of a function. This means a break is same as return. if (follow == null || areMatchingExits(n, follow)) { n.detachFromParent(); reportCodeChange(); return null; } return n; } /** * @return Whether the expression does not produces and can not be affected * by side-effects. */ boolean isPure(Node n) { return n == null || (!NodeUtil.canBeSideEffected(n) && !NodeUtil.mayHaveSideEffects(n)); } /** * @return n or the node following any following finally nodes. */ Node skipFinallyNodes(Node n) { while (n != null && NodeUtil.isTryFinallyNode(n.getParent(), n)) { n = ControlFlowAnalysis.computeFollowNode(n); } return n; } /** * Check whether one exit can be replaced with another. Verify: * 1) They are identical expressions * 2) If an exception is possible that the statements, the original * and the potential replacement are in the same exception handler. */ boolean areMatchingExits(Node nodeThis, Node nodeThat) { return nodeThis.isEquivalentTo(nodeThat) && (!isExceptionPossible(nodeThis) || getExceptionHandler(nodeThis) == getExceptionHandler(nodeThat)); } boolean isExceptionPossible(Node n) { // TODO(johnlenz): maybe use ControlFlowAnalysis.mayThrowException? Preconditions.checkState(n.isReturn() || n.isThrow()); return n.isThrow() || (n.hasChildren() && !NodeUtil.isLiteralValue(n.getLastChild(), true)); } Node getExceptionHandler(Node n) { return ControlFlowAnalysis.getExceptionHandler(n); } /** * Try to minimize NOT nodes such as !(x==y). * * Returns the replacement for n or the original

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> if no change was made */ private Node tryMinimizeNot(Node n) { Node parent = n.getParent(); Node notChild = n.getFirstChild(); // negative operator of the current one : == -> != for instance. int complementOperator; switch (notChild.getType()) { case Token.EQ: complementOperator = Token.NE; break; case Token.NE: complementOperator = Token.EQ; break; case Token.SHEQ: complementOperator = Token.SHNE; break; case Token.SHNE: complementOperator = Token.SHEQ; break; // GT, GE, LT, LE are not handled in this because !(x<NaN) != x>=NaN. default: return n; } Node newOperator = n.removeFirstChild(); newOperator.setType(complementOperator); parent.replaceChild(n, newOperator); reportCodeChange(); return newOperator; } /** * Try turning IF nodes into smaller HOOKs * * Returns the replacement for n or the original if no replacement was * necessary. */ private Node tryMinimizeIf(Node n) { Node parent = n.getParent(); Node cond = n.getFirstChild(); /* If the condition is a literal, we'll let other * optimizations try to remove useless code. */ if (NodeUtil.isLiteralValue(cond, true)) { return n; } Node thenBranch = cond.getNext(); Node elseBranch = thenBranch.getNext(); if (elseBranch == null) { if (isFoldableExpressBlock(thenBranch)) { Node expr = getBlockExpression(thenBranch); if (!late && isPropertyAssignmentInExpression(expr)) { // Keep opportunities for CollapseProperties such as // a.longIdentifier || a.longIdentifier = ... -> var a = ...; // until CollapseProperties has been run. return n; } if (cond.isNot()) { // if(!x)bar(); -> x||bar(); if (isLowerPrecedenceInExpression(cond, OR_PRECEDENCE) && isLowerPrecedenceInExpression(expr.getFirstChild(), OR_PRECEDENCE)) { // It's not okay to add two sets of parentheses. return n; } Node or = IR.or( cond.removeFirstChild(), expr.removeFirstChild()).srcref(n); Node newExpr = NodeUtil.newExpr(or); parent.replaceChild(n, newExpr); reportCodeChange(); return newExpr; } // if(x)foo(); -> x&&foo(); if (isLowerPrecedenceInExpression(cond, AND_PRECEDENCE) && isLowerPrecedenceInExpression(expr.getFirstChild(), AND_PRECEDENCE)) { // One additional set of parentheses is worth the change even if // there is no immediate code size win. However, two extra pair of // {}, we would have to think twice. (unless we know for sure the // we can further optimize its parent. return n; } n.removeChild(cond); Node and = IR.and(cond, expr.removeFirstChild()).srcref(n); Node newExpr = NodeUtil.

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>newExpr(and); parent.replaceChild(n, newExpr); reportCodeChange(); return newExpr; } else { // Try to combine two IF-ELSE if (NodeUtil.isStatementBlock(thenBranch) && thenBranch.hasOneChild()) { Node innerIf = thenBranch.getFirstChild(); if (innerIf.isIf()) { Node innerCond = innerIf.getFirstChild(); Node innerThenBranch = innerCond.getNext(); Node innerElseBranch = innerThenBranch.getNext(); if (innerElseBranch == null && !(isLowerPrecedenceInExpression(cond, AND_PRECEDENCE) && isLowerPrecedenceInExpression(innerCond, AND_PRECEDENCE))) { n.detachChildren(); n.addChildToBack( IR.and( cond, innerCond.detachFromParent()) .srcref(cond)); n.addChildrenToBack(innerThenBranch.detachFromParent()); reportCodeChange(); // Not worth trying to fold the current IF-ELSE into && because // the inner IF-ELSE wasn't able to be folded into && anyways. return n; } } } } return n; } /* TODO(dcc) This modifies the siblings of n, which is undesirable for a * peephole optimization. This should probably get moved to another pass. */ tryRemoveRepeatedStatements(n); // if(!x)foo();else bar(); -> if(x)bar();else foo(); // An additional set of curly braces isn't worth it. if (cond.isNot() && !consumesDanglingElse(elseBranch)) { n.replaceChild(cond, cond.removeFirstChild()); n.removeChild(thenBranch); n.addChildToBack(thenBranch); reportCodeChange(); return n; } // if(x)return 1;else return 2; -> return x?1:2; if (isReturnExpressBlock(thenBranch) && isReturnExpressBlock(elseBranch)) { Node thenExpr = getBlockReturnExpression(thenBranch); Node elseExpr = getBlockReturnExpression(elseBranch); n.removeChild(cond); thenExpr.detachFromParent(); elseExpr.detachFromParent(); // note - we ignore any cases with "return;", technically this // can be converted to "return undefined;" or some variant, but // that does not help code size. Node returnNode = IR.returnNode( IR.hook(cond, thenExpr, elseExpr) .srcref(n)); parent.replaceChild(n, returnNode); reportCodeChange(); return returnNode; } boolean thenBranchIsExpressionBlock = isFoldableExpressBlock(thenBranch); boolean elseBranchIsExpressionBlock = isFoldableExpressBlock(elseBranch); if (thenBranchIsExpressionBlock && elseBranchIsExpressionBlock) { Node thenOp = getBlockExpression(thenBranch).getFirstChild(); Node elseOp = getBlockExpression(elseBranch).getFirstChild(); if (thenOp.getType() == elseOp.getType()) { // if(x)a=1;else a=2; -> a=x?1:2; if (NodeUtil.isAssignmentOp

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>(thenOp)) { Node lhs = thenOp.getFirstChild(); if (areNodesEqualForInlining(lhs, elseOp.getFirstChild()) && // if LHS has side effects, don't proceed [since the optimization // evaluates LHS before cond] // NOTE - there are some circumstances where we can // proceed even if there are side effects... !mayEffectMutableState(lhs)) { n.removeChild(cond); Node assignName = thenOp.removeFirstChild(); Node thenExpr = thenOp.removeFirstChild(); Node elseExpr = elseOp.getLastChild(); elseOp.removeChild(elseExpr); Node hookNode = IR.hook(cond, thenExpr, elseExpr).srcref(n); Node assign = new Node(thenOp.getType(), assignName, hookNode) .srcref(thenOp); Node expr = NodeUtil.newExpr(assign); parent.replaceChild(n, expr); reportCodeChange(); return expr; } } } // if(x)foo();else bar(); -> x?foo():bar() n.removeChild(cond); thenOp.detachFromParent(); elseOp.detachFromParent(); Node expr = IR.exprResult( IR.hook(cond, thenOp, elseOp).srcref(n)); parent.replaceChild(n, expr); reportCodeChange(); return expr; } boolean thenBranchIsVar = isVarBlock(thenBranch); boolean elseBranchIsVar = isVarBlock(elseBranch); // if(x)var y=1;else y=2 -> var y=x?1:2 if (thenBranchIsVar && elseBranchIsExpressionBlock && getBlockExpression(elseBranch).getFirstChild().isAssign()) { Node var = getBlockVar(thenBranch); Node elseAssign = getBlockExpression(elseBranch).getFirstChild(); Node name1 = var.getFirstChild(); Node maybeName2 = elseAssign.getFirstChild(); if (name1.hasChildren() && maybeName2.isName() && name1.getString().equals(maybeName2.getString())) { Node thenExpr = name1.removeChildren(); Node elseExpr = elseAssign.getLastChild().detachFromParent(); cond.detachFromParent(); Node hookNode = IR.hook(cond, thenExpr, elseExpr) .srcref(n); var.detachFromParent(); name1.addChildrenToBack(hookNode); parent.replaceChild(n, var); reportCodeChange(); return var; } // if(x)y=1;else var y=2 -> var y=x?1:2 } else if (elseBranchIsVar && thenBranchIsExpressionBlock && getBlockExpression(thenBranch).getFirstChild().isAssign()) { Node var = getBlockVar(elseBranch); Node thenAssign = getBlockExpression(thenBranch).getFirstChild(); Node maybeName1 = thenAssign.getFirstChild(); Node name2 = var.getFirstChild(); if (name2.hasChildren() && maybeName1.isName() && maybeName1.getString().equals(name2.getString())) { Node thenExpr = thenAssign.getLastChild().detachFromParent();

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> Node elseExpr = name2.removeChildren(); cond.detachFromParent(); Node hookNode = IR.hook(cond, thenExpr, elseExpr) .srcref(n); var.detachFromParent(); name2.addChildrenToBack(hookNode); parent.replaceChild(n, var); reportCodeChange(); return var; } } return n; } /** * Try to remove duplicate statements from IF blocks. For example: * * if (a) { * x = 1; * return true; * } else { * x = 2; * return true; * } * * becomes: * * if (a) { * x = 1; * } else { * x = 2; * } * return true; * * @param n The IF node to examine. */ private void tryRemoveRepeatedStatements(Node n) { Preconditions.checkState(n.isIf()); Node parent = n.getParent(); if (!NodeUtil.isStatementBlock(parent)) { // If the immediate parent is something like a label, we // can't move the statement, so bail. return; } Node cond = n.getFirstChild(); Node trueBranch = cond.getNext(); Node falseBranch = trueBranch.getNext(); Preconditions.checkNotNull(trueBranch); Preconditions.checkNotNull(falseBranch); while (true) { Node lastTrue = trueBranch.getLastChild(); Node lastFalse = falseBranch.getLastChild(); if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.isBlock()) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); if (maybeExpr.isExprResult()) { // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. if (maybeExpr.getFirstChild().isCall()) { Node calledFn = maybeExpr.getFirstChild().getFirstChild(); // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. if (calledFn.isGetElem()) { return false; } else if (calledFn.isGetProp() && calledFn.getLastChild().getString().startsWith("on")) { return false; } } return true; } return false; } } return false; } /** * @return The expression node. */

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** * @return Whether the node is a block with a single statement that is * an return with or without an expression. */ private boolean isReturnBlock(Node n) { if (n.isBlock()) { if (n.hasOneChild()) { Node first = n.getFirstChild(); return first.isReturn(); } } return false; } /** * @return Whether the node is a block with a single statement that is * an return. */ private boolean isReturnExpressBlock(Node n) { if (n.isBlock()) { if (n.hasOneChild()) { Node first = n.getFirstChild(); if (first.isReturn()) { return first.hasOneChild(); } } } return false; } /** * @return Whether the node is a single return statement. */ private boolean isReturnExpression(Node n) { if (n.isReturn()) { return n.hasOneChild(); } return false; } /** * @return The expression that is part of the return. */ private Node getBlockReturnExpression(Node n) { Preconditions.checkState(isReturnExpressBlock(n)); return n.getFirstChild().getFirstChild(); } /** * @return Whether the node is a block with a single statement that is * a VAR declaration of a single variable. */ private boolean isVarBlock(Node n) { if (n.isBlock()) { if (n.hasOneChild()) { Node first = n.getFirstChild(); if (first.isVar()) { return first.hasOneChild(); } } } return false; } /** * @return The var node. */ private Node getBlockVar(Node n) { Preconditions.checkState(isVarBlock(n)); return n.getFirstChild(); } /** * Does a statement consume a 'dangling else'? A statement consumes * a 'dangling else' if an 'else' token following the statement * would be considered by the parser to be part of the statement. */ private boolean consumesDanglingElse(Node n) { while (true) { switch (n.getType()) { case Token.IF: if (n.getChildCount() < 3) { return true; } // This IF node has no else clause. n = n.getLastChild(); continue; case Token.WITH: case Token.WHILE: case Token.FOR: n = n.getLastChild(); continue; default: return false; } } } /** * Does the expression contain an operator with lower precedence than * the argument? */ private boolean isLowerPrecedenceInExpression(Node n, final int precedence) { Predicate<Node> isLowerPrecedencePredicate = new Predicate<Node>() { @Override public boolean apply(Node input) { return NodeUtil.precedence(input.getType()) < precedence; } }; return NodeUtil.has(n, isLowerPre

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>cedencePredicate, DONT_TRAVERSE_FUNCTIONS_PREDICATE); } /** * Whether the node type has lower precedence than "precedence" */ private boolean isLowerPrecedence(Node n, final int precedence) { return NodeUtil.precedence(n.getType()) < precedence; } /** * Whether the node type has higher precedence than "precedence" */ private boolean isHigherPrecedence(Node n, final int precedence) { return NodeUtil.precedence(n.getType()) > precedence; } /** * Does the expression contain a property assignment? */ private boolean isPropertyAssignmentInExpression(Node n) { Predicate<Node> isPropertyAssignmentInExpressionPredicate = new Predicate<Node>() { @Override public boolean apply(Node input) { return (input.isGetProp() && input.getParent().isAssign()); } }; return NodeUtil.has(n, isPropertyAssignmentInExpressionPredicate, DONT_TRAVERSE_FUNCTIONS_PREDICATE); } /** * Try to minimize conditions expressions, as there are additional * assumptions that can be made when it is known that the final result * is a boolean. * * The following transformations are done recursively: * !(x||y) --> !x&&!y * !(x&&y) --> !x||!y * !!x --> x * Thus: * !(x&&!y) --> !x||!!y --> !x||y * * Returns the replacement for n, or the original if no change was made */ private Node tryMinimizeCondition(Node n) { Node parent = n.getParent(); switch (n.getType()) { case Token.NOT: Node first = n.getFirstChild(); switch (first.getType()) { case Token.NOT: { Node newRoot = first.removeFirstChild(); parent.replaceChild(n, newRoot); reportCodeChange(); // No need to traverse, tryMinimizeCondition is called on the // NOT children are handled below. return newRoot; } case Token.AND: case Token.OR: { // !(!x && !y) --> x || y // !(!x || !y) --> x && y // !(!x && y) --> x || !y // !(!x || y) --> x && !y // !(x && !y) --> !x || y // !(x || !y) --> !x && y // !(x && y) --> !x || !y // !(x || y) --> !x && !y Node leftParent = first.getFirstChild(); Node rightParent = first.getLastChild(); Node left, right; // Check special case when such transformation cannot reduce // due to the added () // It only occurs when both of expressions are not NOT expressions if (!leftParent.isNot() && !rightParent.isNot()) { // If an expression has higher precedence than && or ||, // but lower precedence than NOT, an additional () is needed // Thus we do not preceed int op_precedence = NodeUtil.precedence(first.getType()); if ((isLowerPrecedence(leftParent, NOT

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>_PRECEDENCE) && isHigherPrecedence(leftParent, op_precedence)) || (isLowerPrecedence(rightParent, NOT_PRECEDENCE) && isHigherPrecedence(rightParent, op_precedence))) { return n; } } if (leftParent.isNot()) { left = leftParent.removeFirstChild(); } else { leftParent.detachFromParent(); left = IR.not(leftParent).srcref(leftParent); } if (rightParent.isNot()) { right = rightParent.removeFirstChild(); } else { rightParent.detachFromParent(); right = IR.not(rightParent).srcref(rightParent); } int newOp = (first.isAnd()) ? Token.OR : Token.AND; Node newRoot = new Node(newOp, left, right); parent.replaceChild(n, newRoot); reportCodeChange(); // No need to traverse, tryMinimizeCondition is called on the // AND and OR children below. return newRoot; } default: TernaryValue nVal = NodeUtil.getPureBooleanValue(first); if (nVal != TernaryValue.UNKNOWN) { boolean result = nVal.not().toBoolean(true); int equivalentResult = result ? 1 : 0; return maybeReplaceChildWithNumber(n, parent, equivalentResult); } } // No need to traverse, tryMinimizeCondition is called on the NOT // children in the general case in the main post-order traversal. return n; case Token.OR: case Token.AND: { Node left = n.getFirstChild(); Node right = n.getLastChild(); // Because the expression is in a boolean context minimize // the children, this can't be done in the general case. left = tryMinimizeCondition(left); right = tryMinimizeCondition(right); // Remove useless conditionals // Handle four cases: // x || false --> x // x || true --> true // x && true --> x // x && false --> false TernaryValue rightVal = NodeUtil.getPureBooleanValue(right); if (NodeUtil.getPureBooleanValue(right) != TernaryValue.UNKNOWN) { int type = n.getType(); Node replacement = null; boolean rval = rightVal.toBoolean(true); // (x || FALSE) => x // (x && TRUE) => x if (type == Token.OR && !rval || type == Token.AND && rval) { replacement = left; } else if (!mayHaveSideEffects(left)) { replacement = right; } if (replacement != null) { n.detachChildren(); parent.replaceChild(n, replacement); reportCodeChange(); return replacement; } } return n; } case Token.HOOK: { Node condition = n.getFirstChild(); Node trueNode = n.getFirstChild().getNext(); Node falseNode = n.getLastChild(); // Because the expression is in a boolean context minimize // the result children, this can't be done in the general case. // The condition is handled in the general case in #

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>optimizeSubtree trueNode = tryMinimizeCondition(trueNode); falseNode = tryMinimizeCondition(falseNode); // Handle four cases: // x ? true : false --> x // x ? false : true --> !x // x ? true : y --> x || y // x ? y : false --> x && y Node replacement = null; TernaryValue trueNodeVal = NodeUtil.getPureBooleanValue(trueNode); TernaryValue falseNodeVal = NodeUtil.getPureBooleanValue(falseNode); if (trueNodeVal == TernaryValue.TRUE && falseNodeVal == TernaryValue.FALSE) { // Remove useless conditionals, keep the condition condition.detachFromParent(); replacement = condition; } else if (trueNodeVal == TernaryValue.FALSE && falseNodeVal == TernaryValue.TRUE) { // Remove useless conditionals, keep the condition condition.detachFromParent(); replacement = IR.not(condition); } else if (trueNodeVal == TernaryValue.TRUE) { // Remove useless true case. n.detachChildren(); replacement = IR.or(condition, falseNode); } else if (falseNodeVal == TernaryValue.FALSE) { // Remove useless false case n.detachChildren(); replacement = IR.and(condition, trueNode); } if (replacement != null) { parent.replaceChild(n, replacement); n = replacement; reportCodeChange(); } return n; } default: // while(true) --> while(1) TernaryValue nVal = NodeUtil.getPureBooleanValue(n); if (nVal != TernaryValue.UNKNOWN) { boolean result = nVal.toBoolean(true); int equivalentResult = result ? 1 : 0; return maybeReplaceChildWithNumber(n, parent, equivalentResult); } // We can't do anything else currently. return n; } } /** * Replaces a node with a number node if the new number node is not equivalent * to the current node. * * Returns the replacement for n if it was replaced, otherwise returns n. */ private Node maybeReplaceChildWithNumber(Node n, Node parent, int num) { Node newNode = IR.number(num); if (!newNode.isEquivalentTo(n)) { parent.replaceChild(n, newNode); reportCodeChange(); return newNode; } return n; } private static final ImmutableSet<String> STANDARD_OBJECT_CONSTRUCTORS = // String, Number, and Boolean functions return non-object types, whereas // new String, new Number, and new Boolean return object types, so don't // include them here. ImmutableSet.of( "Object", "Array", "RegExp", "Error" ); /** * Fold "new Object()" to "Object()". */ private Node tryFoldStandardConstructors(Node n) { Preconditions.checkState(n.isNew()); // If name normalization has been run then we know that // new Object() does in fact refer to what we think it is // and not some custom-defined Object(). if (isASTNormalized())

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>emoizedScopeCreator */ static class MemoizedScopeCleanupPass implements HotSwapCompilerPass { private final AbstractCompiler compiler; public MemoizedScopeCleanupPass(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { ScopeCreator creator = compiler.getTypedScopeCreator(); if (creator instanceof MemoizedScopeCreator) { MemoizedScopeCreator scopeCreator = (MemoizedScopeCreator) creator; String newSrc = scriptRoot.getSourceFileName(); for (Var var : scopeCreator.getAllSymbols()) { JSType type = var.getType(); if (type != null) { FunctionType fnType = type.toMaybeFunctionType(); if (fnType != null && newSrc.equals(NodeUtil.getSourceName(fnType.getSource()))) { fnType.setSource(null); } } } scopeCreator.removeScopesForScript(originalRoot.getSourceFileName()); } } @Override public void process(Node externs, Node root) { // MemoizedScopeCleanupPass should not do work during process. } } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> @Override void endFunction(boolean statementContext) { super.endFunction(statementContext); if (statementContext) { startNewLine(); } } @Override void beginCaseBody() { super.beginCaseBody(); indent++; endLine(); } @Override void endCaseBody() { super.endCaseBody(); indent--; endStatement(); } @Override void appendOp(String op, boolean binOp) { if (binOp) { if (getLastChar() != ' ' && op.charAt(0) != ',') { append(" "); } append(op); append(" "); } else { append(op); } } /** * If the body of a for loop or the then clause of an if statement has * a single statement, should it be wrapped in a block? * {@inheritDoc} */ @Override boolean shouldPreserveExtraBlocks() { // When pretty-printing, always place the statement in its own block // so it is printed on a separate line. This allows breakpoints to be // placed on the statement. return true; } /** * @return The TRY node for the specified CATCH node. */ private Node getTryForCatch(Node n) { return n.getParent().getParent(); } /** * @return Whether the a line break should be added after the specified * BLOCK. */ @Override boolean breakAfterBlockFor(Node n, boolean isStatementContext) { Preconditions.checkState(n.isBlock()); Node parent = n.getParent(); if (parent != null) { int type = parent.getType(); switch (type) { case Token.DO: // Don't break before 'while' in DO-WHILE statements. return false; case Token.FUNCTION: // FUNCTIONs are handled separately, don't break here. return false; case Token.TRY: // Don't break before catch return n != parent.getFirstChild(); case Token.CATCH: // Don't break before finally return !NodeUtil.hasFinally(getTryForCatch(parent)); case Token.IF: // Don't break before else return n == parent.getLastChild(); } } return true; } @Override void endFile() { maybeEndStatement(); } } static class CompactCodePrinter extends MappedCodePrinter { // The CompactCodePrinter tries to emit just enough newlines to stop there // being lines longer than the threshold. Since the output is going to be // gzipped, it makes sense to try to make the newlines appear in similar // contexts so that gzip can encode them for 'free'. // // This version tries to break the lines at 'preferred' places, which are // between the top-level forms. This works because top-level forms tend to // be more uniform than arbitrary legal contexts. Better compression would // probably require explicit modeling of the gzip algorithm. private final boolean lineBreak; private final boolean preferLineBreakAtEndOfFile; private int lineStartPosition = 0; private int preferredBreakPosition = 0; private int prevCutPosition = 0; private int prevLineStartPosition =

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> people like keeping these semicolons around, // so we'll allow it. if (n.isEmpty() || n.isComma()) { return; } if (parent == null) { return; } // Do not try to remove a block or an expr result. We already handle // these cases when we visit the child, and the peephole passes will // fix up the tree in more clever ways when these are removed. if (n.isExprResult() || n.isBlock()) { return; } // This no-op statement was there so that JSDoc information could // be attached to the name. This check should not complain about it. if (n.isQualifiedName() && n.getJSDocInfo() != null) { return; } boolean isResultUsed = NodeUtil.isExpressionResultUsed(n); boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType()); if (!isResultUsed && (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler()))) { String msg = "This code lacks side-effects. Is there a bug?"; if (n.isString()) { msg = "Is there a missing '+' on the previous line?"; } else if (isSimpleOp) { msg = "The result of the '" + Token.name(n.getType()).toLowerCase() + "' operator is not being used."; } t.getCompiler().report( t.makeError(n, level, USELESS_CODE_ERROR, msg)); // TODO(johnlenz): determine if it is necessary to // try to protect side-effect free statements as well. if (!NodeUtil.isStatement(n)) { problemNodes.add(n); } } } /** * Protect side-effect free nodes by making them parameters * to a extern function call. This call will be removed * after all the optimizations passes have run. */ private void protectSideEffects() { if (!problemNodes.isEmpty()) { addExtern(); for (Node n : problemNodes) { Node name = IR.name(PROTECTOR_FN).srcref(n); name.putBooleanProp(Node.IS_CONSTANT_NAME, true); Node replacement = IR.call(name).srcref(n); replacement.putBooleanProp(Node.FREE_CALL, true); n.getParent().replaceChild(n, replacement); replacement.addChildToBack(n); } compiler.reportCodeChange(); } } private void addExtern() { Node name = IR.name(PROTECTOR_FN); name.putBooleanProp(Node.IS_CONSTANT_NAME, true); Node var = IR.var(name); // Add "@noalias" so we can strip the method when AliasExternals is enabled. JSDocInfoBuilder builder = new JSDocInfoBuilder(false); builder.recordNoAlias(); var.setJSDocInfo(builder.build(var)); CompilerInput input = compiler.getSynthesizedExternsInput(); input.getAstRoot(compiler).addChildrenToBack(var); compiler.reportCodeChange(); }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSTypeExpression; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Prepare the AST before we do any checks or optimizations on it. * * This pass must run. It should bring the AST into a consistent state, * and add annotations where necessary. It should not make any transformations * on the tree that would lose source information, since we need that source * information for checks. * * @author johnlenz@google.com (John Lenz) */ class PrepareAst implements CompilerPass { private final AbstractCompiler compiler; private final boolean checkOnly; PrepareAst(AbstractCompiler compiler) { this(compiler, false); } PrepareAst(AbstractCompiler compiler, boolean checkOnly) { this.compiler = compiler; this.checkOnly = checkOnly; } private void reportChange() { if (checkOnly) { Preconditions.checkState(false, "normalizeNodeType constraints violated"); } } @Override public void process(Node externs, Node root) { if (checkOnly) { normalizeNodeTypes(root); } else { // Don't perform "PrepareAnnotations" when doing checks as // they currently aren't valid during sanity checks. In particular, // they DIRECT_EVAL shouldn't be applied after inlining has been // performed. if (externs != null) { NodeTraversal.traverse( compiler, externs, new PrepareAnnotations(compiler)); } if (root != null) { NodeTraversal.traverse( compiler, root, new PrepareAnnotations(compiler)); } } } /** * Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code. */ private void normalizeNodeTypes(Node n) { normalizeBlocks(n); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { // This pass is run during the CompilerTestCase validation, so this // parent pointer check serves as a more general check. Preconditions.checkState(child.getParent() == n); normalizeNodeTypes(child); } } /** * Add blocks to IF, WHILE, DO, etc. */ private void normalizeBlocks(Node n) { if (NodeUtil.is

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>ControlStructure(n) && !n.isLabel() && !n.isSwitch()) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (NodeUtil.isControlStructureCodeBlock(n,c) && !c.isBlock()) { Node newBlock = IR.block().srcref(n); n.replaceChild(c, newBlock); if (!c.isEmpty()) { newBlock.addChildrenToFront(c); } else { newBlock.setWasEmptyNode(true); } c = newBlock; reportChange(); } } } } /** * Normalize where annotations appear on the AST. Copies * around existing JSDoc annotations as well as internal annotations. */ static class PrepareAnnotations implements NodeTraversal.Callback { private final CodingConvention convention; PrepareAnnotations(AbstractCompiler compiler) { this.convention = compiler.getCodingConvention(); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (n.isObjectLit()) { normalizeObjectLiteralAnnotations(n); } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: annotateCalls(n); break; case Token.FUNCTION: annotateFunctions(n, parent); annotateDispatchers(n, parent); break; } } private void normalizeObjectLiteralAnnotations(Node objlit) { Preconditions.checkState(objlit.isObjectLit()); for (Node key = objlit.getFirstChild(); key != null; key = key.getNext()) { Node value = key.getFirstChild(); normalizeObjectLiteralKeyAnnotations(objlit, key, value); } } /** * There are two types of calls we are interested in calls without explicit * "this" values (what we are call "free" calls) and direct call to eval. */ private void annotateCalls(Node n) { Preconditions.checkState(n.isCall()); // Keep track of of the "this" context of a call. A call without an // explicit "this" is a free call. Node first = n.getFirstChild(); if (!NodeUtil.isGet(first)) { n.putBooleanProp(Node.FREE_CALL, true); } // Keep track of the context in which eval is called. It is important // to distinguish between "(0, eval)()" and "eval()". if (first.isName() && "eval".equals(first.getString())) { first.putBooleanProp(Node.DIRECT_EVAL, true); } } /** * Translate dispatcher info into the property expected node. */ private void annotateDispatchers(Node n, Node parent) { Preconditions.checkState(n.isFunction()); if (parent.getJSDocInfo() != null && parent.getJSDocInfo().isJavaDispatch()) { if (parent.isAssign()) { Preconditions.checkState(parent.getLastChild() == n); n.putBooleanProp(Node.IS_DISPATCHER, true); } } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> /** * In the AST that Rhino gives us, it needs to make a distinction * between JsDoc on the object literal node and JsDoc on the object literal * value. For example, * <pre> * var x = { * / JSDOC / * a: 'b', * c: / JSDOC / 'd' * }; * </pre> * * But in few narrow cases (in particular, function literals), it's * a lot easier for us if the doc is attached to the value. */ private void normalizeObjectLiteralKeyAnnotations( Node objlit, Node key, Node value) { Preconditions.checkState(objlit.isObjectLit()); if (key.getJSDocInfo() != null && value.isFunction()) { value.setJSDocInfo(key.getJSDocInfo()); } } /** * Annotate optional and var_arg function parameters. */ private void annotateFunctions(Node n, Node parent) { JSDocInfo fnInfo = NodeUtil.getFunctionJSDocInfo(n); // Compute which function parameters are optional and // which are var_args. Node args = n.getFirstChild().getNext(); for (Node arg = args.getFirstChild(); arg != null; arg = arg.getNext()) { String argName = arg.getString(); JSTypeExpression typeExpr = fnInfo == null ? null : fnInfo.getParameterType(argName); if (convention.isOptionalParameter(arg) || typeExpr != null && typeExpr.isOptionalArg()) { arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true); } if (convention.isVarArgsParameter(arg) || typeExpr != null && typeExpr.isVarArgs()) { arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true); } } } } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> should point to. * @param superObject The expected super instance type. * @param subObject The sub instance type. */ void expectSuperType(NodeTraversal t, Node n, ObjectType superObject, ObjectType subObject) { FunctionType subCtor = subObject.getConstructor(); ObjectType declaredSuper = subObject.getImplicitPrototype().getImplicitPrototype(); if (!declaredSuper.equals(superObject)) { if (declaredSuper.equals(getNativeType(OBJECT_TYPE))) { registerMismatch(superObject, declaredSuper, report( t.makeError(n, MISSING_EXTENDS_TAG_WARNING, subObject.toString()))); } else { mismatch(t.getSourceName(), n, "mismatch in declaration of superclass type", superObject, declaredSuper); } // Correct the super type. if (!subCtor.hasCachedValues()) { subCtor.setPrototypeBasedOn(superObject); } } } /** * Expect that the first type can be cast to the second type. The first type * should be either a subtype or supertype of the second. * * @param t The node traversal. * @param n The node where warnings should point. * @param type The type being cast from. * @param castType The type being cast to. */ void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) { castType = castType.restrictByNotNullOrUndefined(); type = type.restrictByNotNullOrUndefined(); if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) { registerMismatch(type, castType, report(t.makeError(n, INVALID_CAST, castType.toString(), type.toString()))); } } /** * Expect that the given variable has not been declared with a type. * * @param sourceName The name of the source file we're in. * @param n The node where warnings should point to. * @param parent The parent of {@code n}. * @param var The variable that we're checking. * @param variableName The name of the variable. * @param newType The type being applied to the variable. Mostly just here * for the benefit of the warning. * @return The variable we end up with. Most of the time, this will just * be {@code var}, but in some rare cases we will need to declare * a new var with new source info. */ Var expectUndeclaredVariable(String sourceName, CompilerInput input, Node n, Node parent, Var var, String variableName, JSType newType) { Var newVar = var; boolean allowDupe = false; if (n.isGetProp() || NodeUtil.isObjectLitKey(n, parent)) { JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); } JSType varType = var.getType(); // Only report duplicate declarations that have types. Other duplicates // will be reported by the syntactic scope creator

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.javascript.rhino.Node; /** * An abstract class whose implementations run peephole optimizations: * optimizations that look at a small section of code and either remove * that code (if it is not needed) or replaces it with smaller code. * */ abstract class AbstractPeepholeOptimization { private AbstractCompiler compiler; /** * Given a node to optimize and a traversal, optimize the node. Subclasses * should override to provide their own peephole optimization. * * @param subtree The subtree that will be optimized. * @return The new version of the subtree (or null if the subtree or one of * its parents was removed from the AST). If the subtree has not changed, * this method must return {@code subtree}. */ abstract Node optimizeSubtree(Node subtree); /** * Helper method for reporting an error to the compiler when applying a * peephole optimization. * * @param diagnostic The error type * @param n The node for which the error should be reported */ protected void error(DiagnosticType diagnostic, Node n) { JSError error = JSError.make(NodeUtil.getSourceName(n), n, diagnostic, n.toString()); compiler.report(error); } /** * Helper method for telling the compiler that something has changed. * Subclasses must call these if they have changed the AST. */ protected void reportCodeChange() { Preconditions.checkNotNull(compiler); compiler.reportCodeChange(); } /** * Are the nodes equal for the purpose of inlining? * If type aware optimizations are on, type equality is checked. */ protected boolean areNodesEqualForInlining(Node n1, Node n2) { /* Our implementation delegates to the compiler. We provide this * method because we don't want to expose Compiler to PeepholeOptimizations. */ Preconditions.checkNotNull(compiler); return compiler.areNodesEqualForInlining(n1, n2); } /** * Is the current AST normalized? (e.g. has the Normalize pass been run * and has the Denormalize pass not yet been run?) */ protected boolean isASTNormalized() { Preconditions.checkNotNull(compiler); return compiler.getLifeCycleStage().isNormalized(); } /** * Informs the optimization that a traversal will begin. */ void beginTraversal(AbstractCompiler compiler) { this.

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>compiler = compiler; } /** * Informs the optimization that a traversal has completed. */ void endTraversal(AbstractCompiler compiler) { this.compiler = null; } // NodeUtil's mayEffectMutableState and mayHaveSideEffects need access to the // compiler object, route them through here to give them access. /** * @return Whether the node may create new mutable state, or change existing * state. */ boolean mayEffectMutableState(Node n) { return NodeUtil.mayEffectMutableState(n, compiler); } /** * @return Whether the node may have side effects when executed. */ boolean mayHaveSideEffects(Node n) { return NodeUtil.mayHaveSideEffects(n, compiler); } /** * @return Whether the source code version is ECMAScript 5 or later. * Workarounds for quirks in browsers that do not support ES5 can be * ignored when this is true. */ boolean isEcmaScript5OrGreater() { return compiler != null && compiler.acceptEcmaScript5(); } /** * @return the current coding convention. */ CodingConvention getCodingConvention() { // Note: this assumes a thread safe coding convention object. return compiler.getCodingConvention(); } /** * Check if the specified node is null or is still in the AST. */ @VisibleForTesting static Node validateResult(Node n) { done: { if (n != null && !n.isScript() && (!n.isBlock() || !n.isSyntheticBlock())) { for (Node parent : n.getAncestors()) { if (parent.isScript()) { break done; } } Preconditions.checkState(false); } } return n; } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>); if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.isInterface() || jsDoc.hasThisType() || jsDoc.isOverride())) { return false; } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING_KEY)) { return false; } // Don't traverse functions that are getting lent to a prototype. Node gramps = parent.getParent(); if (NodeUtil.isObjectLitKey(parent, gramps)) { JSDocInfo maybeLends = gramps.getJSDocInfo(); if (maybeLends != null && maybeLends.getLendsName() != null && maybeLends.getLendsName().endsWith(".prototype")) { return false; } } } if (parent != null && parent.isAssign()) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { // Only traverse the right side if it's not an assignment to a prototype // property or subproperty. if (NodeUtil.isGet(lhs)) { if (lhs.isGetProp() && lhs.getLastChild().getString().equals("prototype")) { return false; } Node llhs = lhs.getFirstChild(); if (llhs.isGetProp() && llhs.getLastChild().getString().equals("prototype")) { return false; } } } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isThis() && shouldReportThis(n, parent)) { compiler.report(t.makeError(n, GLOBAL_THIS)); } if (n == assignLhsChild) { assignLhsChild = null; } } private boolean shouldReportThis(Node n, Node parent) { if (assignLhsChild != null) { // Always report a THIS on the left side of an assign. return true; } // Also report a THIS with a property access. return parent != null && NodeUtil.isGet(parent); } /** * Gets a function's JSDoc information, if it has any. Checks for a few * patterns (ellipses show where JSDoc would be): * <pre> * ... function() {} * ... x = function() {}; * var ... x = function

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>.isName()); n.removeChild(n.getFirstChild()); } compiler.reportCodeChange(); } if (n.isCall()) { if (t.inGlobalScope()) { // If there's a function call in the global scope, // we just say it's unsafe and freeze all the defines. // // NOTE(nicksantos): We could be a lot smarter here. For example, // ReplaceOverriddenVars keeps a call graph of all functions and // which functions/variables that they reference, and tries // to statically determine which functions are "safe" and which // are not. But this would be overkill, especially because // the intended use of defines is with config_files, where // all the defines are at the top of the bundle. for (DefineInfo info : assignableDefines.values()) { setDefineInfoNotAssignable(info, t); } assignableDefines.clear(); } } updateAssignAllowedStack(n, false); } /** * Determines whether assignment to a define should be allowed * in the subtree of the given node, and if not, records that fact. * * @param n The node whose subtree we're about to enter or exit. * @param entering True if we're entering the subtree, false otherwise. */ private void updateAssignAllowedStack(Node n, boolean entering) { switch (n.getType()) { case Token.CASE: case Token.FOR: case Token.FUNCTION: case Token.HOOK: case Token.IF: case Token.SWITCH: case Token.WHILE: if (entering) { assignAllowed.push(0); } else { assignAllowed.remove(); } break; } } /** * Determines whether assignment to a define should be allowed * at the current point of the traversal. */ private boolean isAssignAllowed() { return assignAllowed.element() == 1; } /** * Tracks the given define. * * @param t The current traversal, for context. * @param name The full name for this define. * @param value The value assigned to the define. * @param valueParent The parent node of value. * @return Whether we should remove this assignment from the parse tree. */ private boolean processDefineAssignment(NodeTraversal t, String name, Node value, Node valueParent) { if (value == null || !NodeUtil.isValidDefineValue(value, allDefines.keySet())) { compiler.report( t.makeError(value, INVALID_DEFINE_INIT_ERROR, name)); } else if (!isAssignAllowed()) { compiler.report( t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name)); } else { DefineInfo info = allDefines.get(name); if (info == null) { // First declaration of this define. info = new DefineInfo(value, valueParent); allDefines.put(name, info); assignableDefines.put(name, info); } else if (info.recordAssignment(value)) { // The define was already initialized, but this is a safe // re-assignment. return true; } else { // The define

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>Implementations can have side effects (e.g. modifying the parse * tree).</p> * @return whether the children of this node should be visited */ boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent); /** * <p>Visits a node in post order (after its children have been visited). * A node is visited only if all its parents should be traversed * ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p> * <p>Implementations can have side effects (e.g. modifying the parse * tree).</p> */ void visit(NodeTraversal t, Node n, Node parent); } /** * Callback that also knows about scope changes */ public interface ScopedCallback extends Callback { /** * Called immediately after entering a new scope. The new scope can * be accessed through t.getScope() */ void enterScope(NodeTraversal t); /** * Called immediately before exiting a scope. The ending scope can * be accessed through t.getScope() */ void exitScope(NodeTraversal t); } /** * Abstract callback to visit all nodes in post order. */ public abstract static class AbstractPostOrderCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return true; } } /** * Abstract scoped callback to visit all nodes in post order. */ public abstract static class AbstractScopedCallback implements ScopedCallback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return true; } @Override public void enterScope(NodeTraversal t) {} @Override public void exitScope(NodeTraversal t) {} } /** * Abstract callback to visit all nodes but not traverse into function * bodies. */ public abstract static class AbstractShallowCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. return parent == null || !parent.isFunction() || n == parent.getFirstChild(); } } /** * Abstract callback to visit all structure and statement nodes but doesn't * traverse into functions or expressions. */ public abstract static class AbstractShallowStatementCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return parent == null || NodeUtil.isControlStructure(parent) || NodeUtil.isStatementBlock(parent); } } /** * Abstract callback to visit a pruned set of nodes. */ public abstract static class AbstractNodeTypePruningCallback implements Callback { private final Set<Integer> nodeTypes; private final boolean include; /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) { this(nodeTypes, true); } /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include/exclude in the traversal

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> * @param include whether to include or exclude the nodes in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes, boolean include) { this.nodeTypes = nodeTypes; this.include = include; } @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return include == nodeTypes.contains(n.getType()); } } /** * Creates a node traversal using the specified callback interface. */ public NodeTraversal(AbstractCompiler compiler, Callback cb) { this(compiler, cb, new SyntacticScopeCreator(compiler)); } /** * Creates a node traversal using the specified callback interface * and the scope creator. */ public NodeTraversal(AbstractCompiler compiler, Callback cb, ScopeCreator scopeCreator) { this.callback = cb; if (cb instanceof ScopedCallback) { this.scopeCallback = (ScopedCallback) cb; } this.compiler = compiler; this.inputId = null; this.sourceName = ""; this.scopeCreator = scopeCreator; } private void throwUnexpectedException(Exception unexpectedException) { // If there's an unexpected exception, try to get the // line number of the code that caused it. String message = unexpectedException.getMessage(); // TODO(user): It is possible to get more information if curNode or // its parent is missing. We still have the scope stack in which it is still // very useful to find out at least which function caused the exception. if (inputId != null) { message = unexpectedException.getMessage() + "\n" + formatNodeContext("Node", curNode) + (curNode == null ? "" : formatNodeContext("Parent", curNode.getParent())); } compiler.throwInternalError(message, unexpectedException); } private String formatNodeContext(String label, Node n) { if (n == null) { return " " + label + ": NULL"; } return " " + label + "(" + n.toString(false, false, false) + "): " + formatNodePosition(n); } /** * Traverses a parse tree recursively. */ public void traverse(Node root) { try { inputId = NodeUtil.getInputId(root); sourceName = ""; curNode = root; pushScope(root); traverseBranch(root, null); popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } public void traverseRoots(Node ... roots) { traverseRoots(Lists.newArrayList(roots)); } public void traverseRoots(List<Node> roots) { if (roots.isEmpty()) { return; } try { Node scopeRoot = roots.get(0).getParent(); Preconditions.checkState(scopeRoot != null); inputId = NodeUtil.getInputId(scopeRoot); sourceName = ""; curNode = scopeRoot; pushScope(scopeRoot); for (Node root : roots) { Preconditions.checkState(root.getParent() == scopeRoot); traverseBranch(root, scopeRoot); } popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>unexpectedException); } } private static final String MISSING_SOURCE = "[source unknown]"; private String formatNodePosition(Node n) { if (n == null) { return MISSING_SOURCE + "\n"; } int lineNumber = n.getLineno(); int columnNumber = n.getCharno(); String src = compiler.getSourceLine(sourceName, lineNumber); if (src == null) { src = MISSING_SOURCE; } return sourceName + ":" + lineNumber + ":" + columnNumber + "\n" + src + "\n"; } /** * Traverses a parse tree recursively with a scope, starting with the given * root. This should only be used in the global scope. Otherwise, use * {@link #traverseAtScope}. */ void traverseWithScope(Node root, Scope s) { Preconditions.checkState(s.isGlobal()); inputId = null; sourceName = ""; curNode = root; pushScope(s); traverseBranch(root, null); popScope(); } /** * Traverses a parse tree recursively with a scope, starting at that scope's * root. */ void traverseAtScope(Scope s) { Node n = s.getRootNode(); if (n.isFunction()) { // We need to do some extra magic to make sure that the scope doesn't // get re-created when we dive into the function. if (inputId == null) { inputId = NodeUtil.getInputId(n); } sourceName = getSourceName(n); curNode = n; pushScope(s); Node args = n.getFirstChild().getNext(); Node body = args.getNext(); traverseBranch(args, n); traverseBranch(body, n); popScope(); } else { traverseWithScope(n, s); } } /** * Traverses an inner node recursively with a refined scope. An inner node may * be any node with a non {@code null} parent (i.e. all nodes except the * root). * * @param node the node to traverse * @param parent the node's parent, it may not be {@code null} * @param refinedScope the refined scope of the scope currently at the top of * the scope stack or in trivial cases that very scope or {@code null} */ protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) { Preconditions.checkNotNull(parent); if (refinedScope != null && getScope() != refinedScope) { curNode = node; pushScope(refinedScope); traverseBranch(node, parent); popScope(); } else { traverseBranch(node, parent); } } /** * Gets the compiler. */ public Compiler getCompiler() { // TODO(nicksantos): Remove this type cast. This is just temporary // while refactoring. return (Compiler) compiler; } /** * Gets the current line number, or zero if it cannot be determined. The line * number is retrieved lazily as a running time optimization. */ public int getLineNumber() { Node cur = curNode; while (cur !=

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> null) { int line = cur.getLineno(); if (line >=0) { return line; } cur = cur.getParent(); } return 0; } /** * Gets the current input source name. * * @return A string that may be empty, but not null */ public String getSourceName() { return sourceName; } /** * Gets the current input source. */ public CompilerInput getInput() { return compiler.getInput(inputId); } /** * Gets the current input module. */ public JSModule getModule() { CompilerInput input = getInput(); return input == null ? null : input.getModule(); } /** Returns the node currently being traversed. */ public Node getCurrentNode() { return curNode; } /** * Traverses a node recursively. */ public static void traverse( AbstractCompiler compiler, Node root, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverse(root); } /** * Traverses a list of node trees. */ public static void traverseRoots( AbstractCompiler compiler, List<Node> roots, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } public static void traverseRoots( AbstractCompiler compiler, Callback cb, Node ... roots) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } /** * Traverses a branch. */ @SuppressWarnings("fallthrough") private void traverseBranch(Node n, Node parent) { int type = n.getType(); if (type == Token.SCRIPT) { inputId = n.getInputId(); sourceName = getSourceName(n); } curNode = n; if (!callback.shouldTraverse(this, n, parent)) return; switch (type) { case Token.FUNCTION: traverseFunction(n, parent); break; default: for (Node child = n.getFirstChild(); child != null; ) { // child could be replaced, in which case our child node // would no longer point to the true next Node next = child.getNext(); traverseBranch(child, n); child = next; } break; } curNode = n; callback.visit(this, n, parent); } /** * Traverses a function. */ private void traverseFunction(Node n, Node parent) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.isFunction()); final Node fnName = n.getFirstChild(); boolean isFunctionExpression = (parent != null) && NodeUtil.isFunctionExpression(n); if (!isFunctionExpression) { // Functions declarations are in the scope containing the declaration. traverseBranch(fnName, n); } curNode = n; pushScope(n); if (isFunctionExpression) { // Function expression names are only accessible within the function // scope. traverseBranch(fnName, n); } final Node args = fnName.getNext(); final Node body = args.getNext(); // Args traverse

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>Break = false; preferLineBreakAtEndOfFile = false; reportPath = null; tracer = TracerMode.OFF; colorizeErrorOutput = false; errorFormat = ErrorFormat.SINGLELINE; debugFunctionSideEffectsPath = null; externExports = false; nameReferenceReportPath = null; nameReferenceGraphPath = null; // Debugging aliasHandler = NULL_ALIAS_TRANSFORMATION_HANDLER; errorHandler = null; } /** * @return Whether to attempt to remove unused class properties */ public boolean isRemoveUnusedClassProperties() { return removeUnusedClassProperties; } /** * @param removeUnusedClassProperties Whether to attempt to remove * unused class properties */ public void setRemoveUnusedClassProperties(boolean removeUnusedClassProperties) { this.removeUnusedClassProperties = removeUnusedClassProperties; } /** * Returns the map of define replacements. */ public Map<String, Node> getDefineReplacements() { return getReplacementsHelper(defineReplacements); } /** * Returns the map of tweak replacements. */ public Map<String, Node> getTweakReplacements() { return getReplacementsHelper(tweakReplacements); } /** * Creates a map of String->Node from a map of String->Number/String/Boolean. */ private static Map<String, Node> getReplacementsHelper( Map<String, Object> source) { Map<String, Node> map = Maps.newHashMap(); for (Map.Entry<String, Object> entry : source.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); if (value instanceof Boolean) { map.put(name, NodeUtil.booleanNode(((Boolean) value).booleanValue())); } else if (value instanceof Integer) { map.put(name, IR.number(((Integer) value).intValue())); } else if (value instanceof Double) { map.put(name, IR.number(((Double) value).doubleValue())); } else { Preconditions.checkState(value instanceof String); map.put(name, IR.string((String) value)); } } return map; } /** * Sets the value of the {@code @define} variable in JS * to a boolean literal. */ public void setDefineToBooleanLiteral(String defineName, boolean value) { defineReplacements.put(defineName, new Boolean(value)); } /** * Sets the value of the {@code @define} variable in JS to a * String literal. */ public void setDefineToStringLiteral(String defineName, String value) { defineReplacements.put(defineName, value); } /** * Sets the value of the {@code @define} variable in JS to a * number literal. */ public void setDefineToNumberLiteral(String defineName, int value) { defineReplacements.put(defineName, new Integer(value)); } /** * Sets the value of the {@code @define} variable in JS to a * number literal. */ public void setDefineToDoubleLiteral(String defineName, double value) { defineReplacements.put(defineName, new Double(value)); } /**

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> bush formation, module 2 depends * on module 1, and all other modules depend on module 2. */ static JSModule[] createModuleBush(String ... inputs) { Preconditions.checkState(inputs.length > 2); JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[i].addDependency(modules[i == 1 ? 0 : 1]); } return modules; } /** * Generates a list of modules from a list of inputs, such that modules * form a tree formation. In a tree formation, module N depends on * module `floor(N/2)`, So the modules form a balanced binary tree. */ static JSModule[] createModuleTree(String ... inputs) { JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[i].addDependency(modules[(i - 1) / 2]); } return modules; } /** * Generates a list of modules from a list of inputs. Does not generate any * dependencies between the modules. */ static JSModule[] createModules(String... inputs) { JSModule[] modules = new JSModule[inputs.length]; for (int i = 0; i < inputs.length; i++) { JSModule module = modules[i] = new JSModule("m" + i); module.add(SourceFile.fromCode("i" + i, inputs[i])); } return modules; } private static class BlackHoleErrorManager extends BasicErrorManager { private BlackHoleErrorManager(Compiler compiler) { compiler.setErrorManager(this); } @Override public void println(CheckLevel level, JSError error) {} @Override public void printSummary() {} } Compiler createCompiler() { Compiler compiler = new Compiler(); return compiler; } protected void setExpectedSymbolTableError(DiagnosticType type) { this.expectedSymbolTableError = type; } /** Finds the first matching qualified name node in post-traversal order. */ protected final Node findQualifiedNameNode(final String name, Node root) { final List<Node> matches = Lists.newArrayList(); NodeUtil.visitPostOrder(root, new NodeUtil.Visitor() { @Override public void visit(Node n) { if (name.equals(n.getQualifiedName())) { matches.add(n); } } }, Predicates.<Node>alwaysTrue()); return matches.get(0); } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Predicate; import com.google.common.collect.Maps; import com.google.debugging.sourcemap.FilePosition; import com.google.debugging.sourcemap.SourceMapFormat; import com.google.debugging.sourcemap.SourceMapGenerator; import com.google.debugging.sourcemap.SourceMapGeneratorFactory; import com.google.debugging.sourcemap.SourceMapGeneratorV1; import com.google.debugging.sourcemap.SourceMapGeneratorV2; import com.google.javascript.rhino.Node; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; /** * Collects information mapping the generated (compiled) source back to * its original source for debugging purposes. * * @see CodeConsumer * @see CodeGenerator * @see CodePrinter * */ public class SourceMap { public static enum Format { V1 { @Override SourceMap getInstance() { return new SourceMap( SourceMapGeneratorFactory.getInstance(SourceMapFormat.V1)); } }, DEFAULT { @Override SourceMap getInstance() { return new SourceMap( SourceMapGeneratorFactory.getInstance(SourceMapFormat.DEFAULT)); } }, V2 { @Override SourceMap getInstance() { return new SourceMap( SourceMapGeneratorFactory.getInstance(SourceMapFormat.V2)); } }, V3 { @Override SourceMap getInstance() { return new SourceMap( SourceMapGeneratorFactory.getInstance(SourceMapFormat.V3)); } }; abstract SourceMap getInstance(); } /** * Source maps can be very large different levels of detail can be specified. */ public static enum DetailLevel implements Predicate<Node> { // ALL is best when the fullest details are needed for debugging or for // code-origin analysis. ALL { @Override public boolean apply(Node node) { return true; } }, // SYMBOLS is intended to be used for stack trace deobfuscation when full // detail is not needed. SYMBOLS { @Override public boolean apply(Node node) { return node.isCall() || node.isNew() || node.isFunction() || node.isName() || NodeUtil.isGet(node) || NodeUtil.isObjectLitKey(node, node.getParent()) || (node.isString() && NodeUtil.isGet(node.getParent())); } };

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> final boolean sanityCheck; // Whether extern checks emit error. private final boolean strictExternCheck; VarCheck(AbstractCompiler compiler) { this(compiler, false); } VarCheck(AbstractCompiler compiler, boolean sanityCheck) { this.compiler = compiler; this.strictExternCheck = compiler.getErrorLevel( JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR; this.sanityCheck = sanityCheck; } @Override public void process(Node externs, Node root) { // Don't run externs-checking in sanity check mode. Normalization will // remove duplicate VAR declarations, which will make // externs look like they have assigns. if (!sanityCheck) { NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck()); } NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); for (String varName : varsToDeclareInExterns) { createSynthesizedExternVar(varName); } } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { Preconditions.checkState(scriptRoot.isScript()); NodeTraversal t = new NodeTraversal(compiler, this); // Note we use the global scope to prevent wrong "undefined-var errors" on // variables that are defined in other JS files. t.traverseWithScope(scriptRoot, SyntacticScopeCreator.generateUntypedTopScope(compiler)); // TODO(bashir) Check if we need to createSynthesizedExternVar like process. } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isName()) { return; } String varName = n.getString(); // Only a function can have an empty name. if (varName.isEmpty()) { Preconditions.checkState(parent.isFunction()); Preconditions.checkState(NodeUtil.isFunctionExpression(parent)); return; } // Check if this is a declaration for a var that has been declared // elsewhere. If so, mark it as a duplicate. if ((parent.isVar() || NodeUtil.isFunctionDeclaration(parent)) && varsToDeclareInExterns.contains(varName)) { createSynthesizedExternVar(varName); n.addSuppression("duplicate"); } // Check that the var has been declared. Scope scope = t.getScope(); Scope.Var var = scope.getVar(varName); if (var == null) { if (NodeUtil.isFunctionExpression(parent)) { // e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the // current scope. } else { // The extern checks are stricter, don't report a second error. if (!strictExternCheck || !t.getInput().isExtern()) { t.report(n, UNDEFINED_VAR_ERROR, varName); } if (sanityCheck) { throw new IllegalStateException("Unexpected variable " + varName); } else { createSynthesizedExternVar(varName); scope.getGlobalScope().declare(varName, n, null, getSynthesizedExtern

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> nameNode, BasicBlock basicBlock, Scope scope, InputId inputId) { this.nameNode = nameNode; this.basicBlock = basicBlock; this.scope = scope; this.inputId = inputId; this.sourceFile = nameNode.getStaticSourceFile(); } /** * Makes a copy of the current reference using a new Scope instance. */ Reference cloneWithNewScope(Scope newScope) { return new Reference(nameNode, basicBlock, newScope, inputId); } @Override public Var getSymbol() { return scope.getVar(nameNode.getString()); } @Override public Node getNode() { return nameNode; } public InputId getInputId() { return inputId; } @Override public StaticSourceFile getSourceFile() { return sourceFile; } boolean isDeclaration() { Node parent = getParent(); Node grandparent = parent.getParent(); return DECLARATION_PARENTS.contains(parent.getType()) || parent.isParamList() && grandparent.isFunction(); } boolean isVarDeclaration() { return getParent().isVar(); } boolean isHoistedFunction() { return NodeUtil.isHoistedFunctionDeclaration(getParent()); } /** * Determines whether the variable is initialized at the declaration. */ boolean isInitializingDeclaration() { // VAR is the only type of variable declaration that may not initialize // its variable. Catch blocks, named functions, and parameters all do. return isDeclaration() && !getParent().isVar() || nameNode.getFirstChild() != null; } /** * @return For an assignment, variable declaration, or function declaration * return the assigned value, otherwise null. */ Node getAssignedValue() { Node parent = getParent(); return (parent.isFunction()) ? parent : NodeUtil.getAssignedValue(nameNode); } BasicBlock getBasicBlock() { return basicBlock; } Node getParent() { return getNode().getParent(); } Node getGrandparent() { Node parent = getParent(); return parent == null ? null : parent.getParent(); } private static boolean isLhsOfForInExpression(Node n) { Node parent = n.getParent(); if (parent.isVar()) { return isLhsOfForInExpression(parent); } return NodeUtil.isForIn(parent) && parent.getFirstChild() == n; } boolean isSimpleAssignmentToName() { Node parent = getParent(); return parent.isAssign() && parent.getFirstChild() == nameNode; } boolean isLvalue() { Node parent = getParent(); int parentType = parent.getType(); return (parentType == Token.VAR && nameNode.getFirstChild() != null) || parentType == Token.INC || parentType == Token.DEC || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == nameNode) || isLhsOfForInExpression(nameNode); } Scope getScope() { return scope; } } /** * Represents a section of code that is uninterrupted by control structures * (conditional or iterative logic). */ static final class BasicBlock { private

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> final BasicBlock parent; /** * Determines whether the block may not be part of the normal control flow, * but instead "hoisted" to the top of the scope. */ private final boolean isHoisted; /** * Whether this block denotes a function scope. */ private final boolean isFunction; /** * Whether this block denotes a loop. */ private final boolean isLoop; /** * Creates a new block. * @param parent The containing block. * @param root The root node of the block. */ BasicBlock(BasicBlock parent, Node root) { this.parent = parent; // only named functions may be hoisted. this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root); this.isFunction = root.isFunction(); if (root.getParent() != null) { int pType = root.getParent().getType(); this.isLoop = pType == Token.DO || pType == Token.WHILE || pType == Token.FOR; } else { this.isLoop = false; } } BasicBlock getParent() { return parent; } /** * Determines whether this block is equivalent to the very first block that * is created when reference collection traversal enters global scope. Note * that when traversing a single script in a hot-swap fashion a new instance * of {@code BasicBlock} is created. * * @return true if this is global scope block. */ boolean isGlobalScopeBlock() { return getParent() == null; } /** * Determines whether this block is guaranteed to begin executing before * the given block does. */ boolean provablyExecutesBefore(BasicBlock thatBlock) { // If thatBlock is a descendant of this block, and there are no hoisted // blocks between them, then this block must start before thatBlock. BasicBlock currentBlock; for (currentBlock = thatBlock; currentBlock != null && currentBlock != this; currentBlock = currentBlock.getParent()) { if (currentBlock.isHoisted) { return false; } } if (currentBlock == this) { return true; } if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) { return true; } return false; } } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> Token.SETTER_DEF: // Object literal keys are handled with OBJECTLIT break; case Token.ARRAYLIT: ensureTyped(t, n, ARRAY_TYPE); break; case Token.REGEXP: ensureTyped(t, n, REGEXP_TYPE); break; case Token.GETPROP: visitGetProp(t, n, parent); typeable = !(parent.isAssign() && parent.getFirstChild() == n); break; case Token.GETELEM: visitGetElem(t, n); // The type of GETELEM is always unknown, so no point counting that. // If that unknown leaks elsewhere (say by an assignment to another // variable), then it will be counted. typeable = false; break; case Token.VAR: visitVar(t, n); typeable = false; break; case Token.NEW: visitNew(t, n); typeable = true; break; case Token.CALL: visitCall(t, n); typeable = !parent.isExprResult(); break; case Token.RETURN: visitReturn(t, n); typeable = false; break; case Token.DEC: case Token.INC: left = n.getFirstChild(); validator.expectNumber( t, left, getJSType(left), "increment/decrement"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.NOT: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.VOID: ensureTyped(t, n, VOID_TYPE); break; case Token.TYPEOF: ensureTyped(t, n, STRING_TYPE); break; case Token.BITNOT: childType = getJSType(n.getFirstChild()); if (!childType.matchesInt32Context()) { report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()), childType.toString()); } ensureTyped(t, n, NUMBER_TYPE); break; case Token.POS: case Token.NEG: left = n.getFirstChild(); validator.expectNumber(t, left, getJSType(left), "sign operator"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: { leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); // We do not want to warn about explicit comparisons to VOID. People // often do this if they think their type annotations screwed up. // // We do want to warn about cases where people compare things like // (Array|null) == (Function|null) // because it probably means they screwed up. // // This heuristic here is not perfect, but should catch cases we // care about without too many false negatives. JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined(); JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined(); TernaryValue result = TernaryValue.UNKNOWN; if (n.getType() == Token.

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>Of( t, assign, getJSType(rvalue), expectedType, object, property); checkPropertyInheritanceOnGetpropAssign( t, assign, object, property, info, expectedType); return; } } } // If we couldn't get the property type with normal object property // lookups, then check inheritance anyway with the unknown type. checkPropertyInheritanceOnGetpropAssign( t, assign, object, property, info, getNativeType(UNKNOWN_TYPE)); } // Check qualified name sets to 'object' and 'object.property'. // This can sometimes handle cases when the type of 'object' is not known. // e.g., // var obj = createUnknownType(); // /** @type {number} */ obj.foo = true; JSType leftType = getJSType(lvalue); if (lvalue.isQualifiedName()) { // variable with inferred type case JSType rvalueType = getJSType(assign.getLastChild()); Var var = t.getScope().getVar(lvalue.getQualifiedName()); if (var != null) { if (var.isTypeInferred()) { return; } if (NodeUtil.getRootOfQualifiedName(lvalue).isThis() && t.getScope() != var.getScope()) { // Don't look at "this.foo" variables from other scopes. return; } if (var.getType() != null) { leftType = var.getType(); } } } // Fall through case for arbitrary LHS and arbitrary RHS. Node rightChild = assign.getLastChild(); JSType rightType = getJSType(rightChild); if (validator.expectCanAssignTo( t, assign, rightType, leftType, "assignment")) { ensureTyped(t, assign, rightType); } else { ensureTyped(t, assign); } } private void checkPropertyInheritanceOnGetpropAssign( NodeTraversal t, Node assign, Node object, String property, JSDocInfo info, JSType propertyType) { // Inheritance checks for prototype properties. // // TODO(nicksantos): This isn't the right place to do this check. We // really want to do this when we're looking at the constructor. // We'd find all its properties and make sure they followed inheritance // rules, like we currently do for @implements to make sure // all the methods are implemented. // // As-is, this misses many other ways to override a property. // // object.prototype.property = ...; if (object.isGetProp()) { Node object2 = object.getFirstChild(); String property2 = NodeUtil.getStringValue(object.getLastChild()); if ("prototype".equals(property2)) { JSType jsType = getJSType(object2); if (jsType.isFunctionType()) { FunctionType functionType = jsType.toMaybeFunctionType(); if (functionType.isConstructor() || functionType.isInterface()) { checkDeclaredPropertyInheritance( t, assign, functionType, property, info, propertyType); } } } } } /** * Visits an object literal field definition <code>key : value</code>.

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> * * If the <code>lvalue</code> is a prototype modification, we change the * schema of the object type it is referring to. * * @param t the traversal * @param key the assign node */ private void visitObjLitKey(NodeTraversal t, Node key, Node objlit) { // Do not validate object lit value types in externs. We don't really care, // and it makes it easier to generate externs. if (objlit.isFromExterns()) { ensureTyped(t, key); return; } // TODO(johnlenz): Validate get and set function declarations are valid // as is the functions can have "extraneous" bits. // For getter and setter property definitions the // r-value type != the property type. Node rvalue = key.getFirstChild(); JSType rightType = NodeUtil.getObjectLitKeyTypeFromValueType( key, getJSType(rvalue)); if (rightType == null) { rightType = getNativeType(UNKNOWN_TYPE); } Node owner = objlit; // Validate value is assignable to the key type. JSType keyType = getJSType(key); JSType allowedValueType = keyType; if (allowedValueType.isEnumElementType()) { allowedValueType = allowedValueType.toMaybeEnumElementType().getPrimitiveType(); } boolean valid = validator.expectCanAssignToPropertyOf(t, key, rightType, allowedValueType, owner, NodeUtil.getObjectLitKeyName(key)); if (valid) { ensureTyped(t, key, rightType); } else { ensureTyped(t, key); } // Validate that the key type is assignable to the object property type. // This is necessary as the objlit may have been cast to a non-literal // object type. // TODO(johnlenz): consider introducing a CAST node to the AST (or // perhaps a parentheses node). JSType objlitType = getJSType(objlit); ObjectType type = ObjectType.cast( objlitType.restrictByNotNullOrUndefined()); if (type != null) { String property = NodeUtil.getObjectLitKeyName(key); if (type.hasProperty(property) && !type.isPropertyTypeInferred(property) && !propertyIsImplicitCast(type, property)) { validator.expectCanAssignToPropertyOf( t, key, keyType, type.getPropertyType(property), owner, property); } return; } } /** * Returns true if any type in the chain has an implicitCast annotation for * the given property. */ private boolean propertyIsImplicitCast(ObjectType type, String prop) { for (; type != null; type = type.getImplicitPrototype()) { JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop); if (docInfo != null && docInfo.isImplicitCast()) { return true; } } return false; } /** * Given a constructor type and a property name, check that the property has * the JSDoc annotation @override iff the property is declared on a * superclass. Several checks regarding inheritance correctness are also * performed. */ private void checkDeclared

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>toString(), propertyType.toString())); } } else if (superInterfaceHasDeclaredProperty) { // there is an super interface property for (ObjectType interfaceType : ctorType.getExtendedInterfaces()) { if (interfaceType.hasProperty(propertyName)) { JSType superPropertyType = interfaceType.getPropertyType(propertyName); if (!propertyType.canAssignTo(superPropertyType)) { topInstanceType = interfaceType.getConstructor(). getTopMostDefiningType(propertyName); compiler.report( t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH, propertyName, topInstanceType.toString(), superPropertyType.toString(), propertyType.toString())); } } } } else if (!foundInterfaceProperty && !superClassHasProperty && !superInterfaceHasProperty) { // there is no superclass nor interface implementation compiler.report( t.makeError(n, UNKNOWN_OVERRIDE, propertyName, ctorType.getInstanceType().toString())); } } /** * Given a constructor or an interface type, find out whether the unknown * type is a supertype of the current type. */ private static boolean hasUnknownOrEmptySupertype(FunctionType ctor) { Preconditions.checkArgument(ctor.isConstructor() || ctor.isInterface()); Preconditions.checkArgument(!ctor.isUnknownType()); // The type system should notice inheritance cycles on its own // and break the cycle. while (true) { ObjectType maybeSuperInstanceType = ctor.getPrototype().getImplicitPrototype(); if (maybeSuperInstanceType == null) { return false; } if (maybeSuperInstanceType.isUnknownType() || maybeSuperInstanceType.isEmptyType()) { return true; } ctor = maybeSuperInstanceType.getConstructor(); if (ctor == null) { return false; } Preconditions.checkState(ctor.isConstructor() || ctor.isInterface()); } } /** * Visits an ASSIGN node for cases such as * <pre> * interface.property2.property = ...; * </pre> */ private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object, String property, Node lvalue, Node rvalue) { JSType rvalueType = getJSType(rvalue); // Only 2 values are allowed for methods: // goog.abstractMethod // function () {}; // or for properties, no assignment such as: // InterfaceFoo.prototype.foobar; String abstractMethodName = compiler.getCodingConvention().getAbstractMethodName(); if (!rvalueType.isFunctionType()) { // This is bad i18n style but we don't localize our compiler errors. String abstractMethodMessage = (abstractMethodName != null) ? ", or " + abstractMethodName : ""; compiler.report( t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION, abstractMethodMessage)); } if (assign.getLastChild().isFunction() && !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) { compiler.report( t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY, abstractMethodName)); } } /** * Visits a NAME node. * * @

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> would check if the * property *can be undefined*. */ private void checkPropertyAccess(JSType childType, String propName, NodeTraversal t, Node n) { // If the property type is unknown, check the object type to see if it // can ever be defined. We explicitly exclude CHECKED_UNKNOWN (for // properties where we've checked that it exists, or for properties on // objects that aren't in this binary). JSType propType = getJSType(n); if (propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) { childType = childType.autobox(); ObjectType objectType = ObjectType.cast(childType); if (objectType != null) { // We special-case object types so that checks on enums can be // much stricter, and so that we can use hasProperty (which is much // faster in most cases). if (!objectType.hasProperty(propName) || objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) { if (objectType instanceof EnumType) { report(t, n, INEXISTENT_ENUM_ELEMENT, propName); } else { checkPropertyAccessHelper(objectType, propName, t, n); } } } else { checkPropertyAccessHelper(childType, propName, t, n); } } } private void checkPropertyAccessHelper(JSType objectType, String propName, NodeTraversal t, Node n) { if (!objectType.isEmptyType() && reportMissingProperties && !isPropertyTest(n)) { if (!typeRegistry.canPropertyBeDefined(objectType, propName)) { report(t, n, INEXISTENT_PROPERTY, propName, validator.getReadableJSTypeName(n.getFirstChild(), true)); } } } /** * Determines whether this node is testing for the existence of a property. * If true, we will not emit warnings about a missing property. * * @param getProp The GETPROP being tested. */ private boolean isPropertyTest(Node getProp) { Node parent = getProp.getParent(); switch (parent.getType()) { case Token.CALL: return parent.getFirstChild() != getProp && compiler.getCodingConvention().isPropertyTestFunction(parent); case Token.IF: case Token.WHILE: case Token.DO: case Token.FOR: return NodeUtil.getConditionExpression(parent) == getProp; case Token.INSTANCEOF: case Token.TYPEOF: return true; case Token.AND: case Token.HOOK: return parent.getFirstChild() == getProp; case Token.NOT: return parent.getParent().isOr() && parent.getParent().getFirstChild() == parent; } return false; } /** * Visits a GETELEM node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitGetElem(NodeTraversal t, Node n) { Node left = n.getFirstChild(); Node right = n.getLastChild();

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> that this must be a var_args function. if (parameters.hasNext()) { parameter = parameters.next(); } argument = arguments.next(); ordinal++; validator.expectArgumentMatchesParameter(t, argument, getJSType(argument), getJSType(parameter), call, ordinal); } int numArgs = call.getChildCount() - 1; int minArgs = functionType.getMinArguments(); int maxArgs = functionType.getMaxArguments(); if (minArgs > numArgs || maxArgs < numArgs) { report(t, call, WRONG_ARGUMENT_COUNT, validator.getReadableJSTypeName(call.getFirstChild(), false), String.valueOf(numArgs), String.valueOf(minArgs), maxArgs != Integer.MAX_VALUE ? " and no more than " + maxArgs + " argument(s)" : ""); } } /** * Visits a RETURN node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitReturn(NodeTraversal t, Node n) { Node function = t.getEnclosingFunction(); // This is a misplaced return, but the real JS will fail to compile, // so let it go. if (function == null) { return; } JSType jsType = getJSType(function); if (jsType.isFunctionType()) { FunctionType functionType = jsType.toMaybeFunctionType(); JSType returnType = functionType.getReturnType(); // if no return type is specified, undefined must be returned // (it's a void function) if (returnType == null) { returnType = getNativeType(VOID_TYPE); } // fetching the returned value's type Node valueNode = n.getFirstChild(); JSType actualReturnType; if (valueNode == null) { actualReturnType = getNativeType(VOID_TYPE); valueNode = n; } else { actualReturnType = getJSType(valueNode); } // verifying validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType, "inconsistent return type"); } } /** * This function unifies the type checking involved in the core binary * operators and the corresponding assignment operators. The representation * used internally is such that common code can handle both kinds of * operators easily. * * @param op The operator. * @param t The traversal object, needed to report errors. * @param n The node being checked. */ private void visitBinaryOperator(int op, NodeTraversal t, Node n) { Node left = n.getFirstChild(); JSType leftType = getJSType(left); Node right = n.getLastChild(); JSType rightType = getJSType(right); switch (op) { case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.LSH: case Token.RSH: case Token.ASSIGN_URSH: case Token.URSH: if (!leftType.matchesInt32Context()) { report(t, left, BIT_OPERATION, Node

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>Util.opToStr(n.getType()), leftType.toString()); } if (!rightType.matchesUint32Context()) { report(t, right, BIT_OPERATION, NodeUtil.opToStr(n.getType()), rightType.toString()); } break; case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_SUB: case Token.DIV: case Token.MOD: case Token.MUL: case Token.SUB: validator.expectNumber(t, left, leftType, "left operand"); validator.expectNumber(t, right, rightType, "right operand"); break; case Token.ASSIGN_BITAND: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITOR: case Token.BITAND: case Token.BITXOR: case Token.BITOR: validator.expectBitwiseable(t, left, leftType, "bad left operand to bitwise operator"); validator.expectBitwiseable(t, right, rightType, "bad right operand to bitwise operator"); break; case Token.ASSIGN_ADD: case Token.ADD: break; default: report(t, n, UNEXPECTED_TOKEN, Token.name(op)); } ensureTyped(t, n); } /** * <p>Checks enum aliases. * * <p>We verify that the enum element type of the enum used * for initialization is a subtype of the enum element type of * the enum the value is being copied in.</p> * * <p>Example:</p> * <pre>var myEnum = myOtherEnum;</pre> * * <p>Enum aliases are irregular, so we need special code for this :(</p> * * @param value the value used for initialization of the enum */ private void checkEnumAlias( NodeTraversal t, JSDocInfo declInfo, Node value) { if (declInfo == null || !declInfo.hasEnumParameterType()) { return; } JSType valueType = getJSType(value); if (!valueType.isEnumType()) { return; } EnumType valueEnumType = valueType.toMaybeEnumType(); JSType valueEnumPrimitiveType = valueEnumType.getElementsType().getPrimitiveType(); validator.expectCanAssignTo(t, value, valueEnumPrimitiveType, declInfo.getEnumParameterType().evaluate(t.getScope(), typeRegistry), "incompatible enum element types"); } /** * This method gets the JSType from the Node argument and verifies that it is * present. */ private JSType getJSType(Node n) { JSType jsType = n.getJSType(); if (jsType == null) { // TODO(nicksantos): This branch indicates a compiler bug, not worthy of // halting the compilation but we should log this and analyze to track // down why it happens. This is not critical and will be resolved over // time as the type checker is extended. return getNativeType(UNKNOWN_TYPE); } else { return jsType; } } // TODO(nicks

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.jscomp.regex.RegExpTree; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.Node; /** * Look for references to the global RegExp object that would cause * regular expressions to be unoptimizable, and checks that regular expressions * are syntactically valid. * * @author johnlenz@google.com (John Lenz) */ class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType REGEXP_REFERENCE = DiagnosticType.warning("JSC_REGEXP_REFERENCE", "References to the global RegExp object prevents " + "optimization of regular expressions."); static final DiagnosticType MALFORMED_REGEXP = DiagnosticType.warning( "JSC_MALFORMED_REGEXP", "Malformed Regular Expression: {0}"); private final AbstractCompiler compiler; private boolean globalRegExpPropertiesUsed = false; public boolean isGlobalRegExpPropertiesUsed() { return globalRegExpPropertiesUsed; } public CheckRegExp(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isReferenceName(n)) { String name = n.getString(); if (name.equals("RegExp") && t.getScope().getVar(name) == null) { int parentType = parent.getType(); boolean first = (n == parent.getFirstChild()); if (!((parentType == Token.NEW && first) || (parentType == Token.CALL && first) || (parentType == Token.INSTANCEOF && !first))) { t.report(n, REGEXP_REFERENCE); globalRegExpPropertiesUsed = true; } } // Check the syntax of regular expression patterns. } else if (n.isRegExp()) { String pattern = n.getFirstChild().getString(); String flags = n.getChildCount() == 2 ? n.getLastChild().getString() : ""; try { RegExpTree.parseRegExp(pattern, flags); } catch (IllegalArgumentException ex) { t.report(n, MALFORMED_REGEXP, ex.getMessage()); } } } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> that checks variables for redeclaration or early references * just after they go out of scope. */ private class ReferenceCheckingBehavior implements Behavior { @Override public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) { // TODO(bashir) In hot-swap version this means that for global scope we // only go through all global variables accessed in the modified file not // all global variables. This should be fixed. // Check all vars after finishing a scope for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); checkVar(t, v, referenceMap.getReferences(v).references); } } /** * If the variable is declared more than once in a basic block, generate a * warning. Also check if a variable is used in a given scope before it is * declared, which suggest a likely error. Relies on the fact that * references is in parse-tree order. */ private void checkVar(NodeTraversal t, Var v, List<Reference> references) { blocksWithDeclarations.clear(); boolean isDeclaredInScope = false; boolean isUnhoistedNamedFunction = false; Reference hoistedFn = null; // Look for hoisted functions. for (Reference reference : references) { if (reference.isHoistedFunction()) { blocksWithDeclarations.add(reference.getBasicBlock()); isDeclaredInScope = true; hoistedFn = reference; break; } else if (NodeUtil.isFunctionDeclaration( reference.getNode().getParent())) { isUnhoistedNamedFunction = true; } } for (Reference reference : references) { if (reference == hoistedFn) { continue; } BasicBlock basicBlock = reference.getBasicBlock(); boolean isDeclaration = reference.isDeclaration(); boolean allowDupe = SyntacticScopeCreator.hasDuplicateDeclarationSuppression( reference.getNode(), v); if (isDeclaration && !allowDupe) { // Look through all the declarations we've found so far, and // check if any of them are before this block. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (declaredBlock.provablyExecutesBefore(basicBlock)) { // TODO(johnlenz): Fix AST generating clients that so they would // have property StaticSourceFile attached at each node. Or // better yet, make sure the generated code never violates // the requirement to pass aggressive var check! String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), checkLevel, REDECLARED_VARIABLE, v.name)); break; } } } if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) { // Only allow an unhoisted named function to be used within the // block it is declared. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (!declaredBlock.provablyExecutesBefore(basicBlock)) { String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), AMBIGUOUS_FUNCTION_DECL, v.name));

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> break; } } } if (!isDeclaration && !isDeclaredInScope) { // Don't check the order of refer in externs files. if (!reference.getNode().isFromExterns()) { // Special case to deal with var goog = goog || {} Node grandparent = reference.getGrandparent(); if (grandparent.isName() && grandparent.getString() == v.name) { continue; } // Only generate warnings if the scopes do not match in order // to deal with possible forward declarations and recursion if (reference.getScope() == v.scope) { String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), checkLevel, UNDECLARED_REFERENCE, v.name)); } } } if (isDeclaration) { blocksWithDeclarations.add(basicBlock); isDeclaredInScope = true; } } } } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> validateBlock(n.getLastChild()); } private void validateFunctionExpression(Node n) { validateNodeType(Token.FUNCTION, n); validateChildCount(n, 3); validateOptionalName(n.getFirstChild()); validateParameters(n.getChildAtIndex(1)); validateBlock(n.getLastChild()); } private void validateParameters(Node n) { validateNodeType(Token.PARAM_LIST, n); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateName(c); } } private void validateCall(Node n) { validateNodeType(Token.CALL, n); validateMinimumChildCount(n, 1); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateExpression(c); } } private void validateNew(Node n) { validateNodeType(Token.NEW, n); validateMinimumChildCount(n, 1); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { validateExpression(c); } } private void validateVar(Node n) { validateNodeType(Token.VAR, n); this.validateMinimumChildCount(n, 1); for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { // Don't use the validateName here as the NAME is allowed to have // a child. validateNodeType(Token.NAME, c); validateNonEmptyString(c); validateMaximumChildCount(c, 1); if (c.hasChildren()) { validateExpression(c.getFirstChild()); } } } private void validateFor(Node n) { validateNodeType(Token.FOR, n); validateMinimumChildCount(n, 3); validateMaximumChildCount(n, 4); if (NodeUtil.isForIn(n)) { // FOR-IN validateChildCount(n, 3); validateVarOrAssignmentTarget(n.getFirstChild()); validateExpression(n.getChildAtIndex(1)); } else { // FOR validateChildCount(n, 4); validateVarOrOptionalExpression(n.getFirstChild()); validateOptionalExpression(n.getChildAtIndex(1)); validateOptionalExpression(n.getChildAtIndex(2)); } validateBlock(n.getLastChild()); } private void validateVarOrOptionalExpression(Node n) { if (n.isVar()) { validateVar(n); } else { validateOptionalExpression(n); } } private void validateVarOrAssignmentTarget(Node n) { if (n.isVar()) { // Only one NAME can be declared for FOR-IN expressions. this.validateChildCount(n, 1); validateVar(n); } else { validateAssignmentTarget(n); } } private void validateWith(Node n) { validateNodeType(Token.WITH, n); validateChildCount(n, 2); validateExpression(n.getFirstChild()); validateBlock(n.getLastChild()); } private void validateWhile(Node n) { validateNodeType(Token.WHILE, n); validateChildCount(n

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * <p>The syntactic scope creator scans the parse tree to create a Scope object * containing all the variable declarations in that scope.</p> * * <p>This implementation is not thread-safe.</p> * */ class SyntacticScopeCreator implements ScopeCreator { private final AbstractCompiler compiler; private Scope scope; private InputId inputId; private final RedeclarationHandler redeclarationHandler; // The arguments variable is special, in that it's declared in every local // scope, but not explicitly declared. private static final String ARGUMENTS = "arguments"; public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR = DiagnosticType.error( "JSC_VAR_MULTIPLY_DECLARED_ERROR", "Variable {0} first declared in {1}"); public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR = DiagnosticType.error( "JSC_VAR_ARGUMENTS_SHADOWED_ERROR", "Shadowing \"arguments\" is not allowed"); /** * Creates a ScopeCreator. */ SyntacticScopeCreator(AbstractCompiler compiler) { this.compiler = compiler; this.redeclarationHandler = new DefaultRedeclarationHandler(); } SyntacticScopeCreator( AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) { this.compiler = compiler; this.redeclarationHandler = redeclarationHandler; } @Override public Scope createScope(Node n, Scope parent) { inputId = null; if (parent == null) { scope = new Scope(n, compiler); } else { scope = new Scope(parent, n); } scanRoot(n, parent); inputId = null; Scope returnedScope = scope; scope = null; return returnedScope; } private void scanRoot(Node n, Scope parent) { if (n.isFunction()) { if (inputId == null) { inputId = NodeUtil.getInputId(n); // TODO(johnlenz): inputId maybe null if the FUNCTION node is detached // from the AST. // Is it meaningful to build a scope for detached FUNCTION node? } final Node fnNameNode = n.getFirstChild(); final Node args

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> = fnNameNode.getNext(); final Node body = args.getNext(); // Bleed the function name into the scope, if it hasn't // been declared in the outer scope. String fnName = fnNameNode.getString(); if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) { declareVar(fnNameNode); } // Args: Declare function variables Preconditions.checkState(args.isParamList()); for (Node a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.isName()); declareVar(a); } // Body scanVars(body, n); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n, null); } } /** * Scans and gather variables declarations under a Node */ private void scanVars(Node n, Node parent) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); declareVar(child); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(n.getFirstChild()); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().isName()); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var); scanVars(block, n); return; // only one child to scan case Token.SCRIPT: inputId = n.getInputId(); Preconditions.checkNotNull(inputId); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); scanVars(child, n); child = next; } } } /** * Interface for injectable duplicate handling. */ interface RedeclarationHandler { void onRedeclaration( Scope s, String name, Node n, CompilerInput input); } /** * The default handler for duplicate declarations. */ private class DefaultRedeclarationHandler implements RedeclarationHandler { @Override public void onRedeclaration( Scope s, String name, Node n, CompilerInput input) { Node parent = n.getParent(); // Don't allow multiple variables to be declared at the top-level scope if (scope.isGlobal()) { Scope.Var origVar = scope.getVar

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>(name); Node origParent = origVar.getParentNode(); if (origParent.isCatch() && parent.isCatch()) { // Okay, both are 'catch(x)' variables. return; } boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar); if (!allowDupe) { compiler.report( JSError.make(NodeUtil.getSourceName(n), n, VAR_MULTIPLY_DECLARED_ERROR, name, (origVar.input != null ? origVar.input.getName() : "??"))); } } else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) { // Disallow shadowing "arguments" as we can't handle with our current // scope modeling. compiler.report( JSError.make(NodeUtil.getSourceName(n), n, VAR_ARGUMENTS_SHADOWED_ERROR)); } } } /** * Declares a variable. * * @param n The node corresponding to the variable name. */ private void declareVar(Node n) { Preconditions.checkState(n.isName()); CompilerInput input = compiler.getInput(inputId); String name = n.getString(); if (scope.isDeclared(name, false) || (scope.isLocal() && name.equals(ARGUMENTS))) { redeclarationHandler.onRedeclaration( scope, name, n, input); } else { scope.declare(name, n, null, input); } } /** * @param n The name node to check. * @param origVar The associated Var. * @return Whether duplicated declarations warnings should be suppressed * for the given node. */ static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); Node origParent = origVar.getParentNode(); JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } if (info != null && info.getSuppressions().contains("duplicate")) { return true; } info = origVar.nameNode.getJSDocInfo(); if (info == null) { info = origParent.getJSDocInfo(); } return (info != null && info.getSuppressions().contains("duplicate")); } /** * Generates an untyped global scope from the root of AST of compiler (which * includes externs). * * @param compiler The compiler for which the scope is generated. * @return The new untyped global scope generated as a result of this call. */ static Scope generateUntypedTopScope(AbstractCompiler compiler) { return new SyntacticScopeCreator(compiler).createScope(compiler.getRoot(), null); } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.ObjectType; import java.nio.charset.Charset; import java.util.Set; /** * A code generator that outputs type annotations for functions and * constructors. */ class TypedCodeGenerator extends CodeGenerator { TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) { super(consumer, outputCharset); } @Override void add(Node n, Context context) { Node parent = n.getParent(); if (parent != null && (parent.isBlock() || parent.isScript())) { if (n.isFunction()) { add(getFunctionAnnotation(n)); } else if (n.isExprResult() && n.getFirstChild().isAssign()) { Node rhs = n.getFirstChild().getLastChild(); add(getTypeAnnotation(rhs)); } else if (n.isVar() && n.getFirstChild().getFirstChild() != null) { add(getTypeAnnotation(n.getFirstChild().getFirstChild())); } } super.add(n, context); } private String getTypeAnnotation(Node node) { // Only add annotations for things with JSDoc, or function literals. JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(node); if (jsdoc == null && !node.isFunction()) { return ""; } JSType type = node.getJSType(); if (type == null) { return ""; } else if (type.isFunctionType()) { return getFunctionAnnotation(node); } else if (type.isEnumType()) { return "/** @enum {" + type.toMaybeEnumType().getElementsType().toAnnotationString() + "} */\n"; } else if (!type.isUnknownType() && !type.isEmptyType() && !type.isVoidType() && !type.isFunctionPrototypeType()) { return "/** @type {" + node.getJSType().toAnnotationString() + "} */\n"; } else { return ""; } } /** * @param fnNode A node for a function for which to generate a type annotation

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> */ private String getFunctionAnnotation(Node fnNode) { Preconditions.checkState(fnNode.isFunction()); StringBuilder sb = new StringBuilder("/**\n"); JSType type = fnNode.getJSType(); if (type == null || type.isUnknownType()) { return ""; } FunctionType funType = type.toMaybeFunctionType(); // We need to use the child nodes of the function as the nodes for the // parameters of the function type do not have the real parameter names. // FUNCTION // NAME // LP // NAME param1 // NAME param2 if (fnNode != null) { Node paramNode = NodeUtil.getFunctionParameters(fnNode).getFirstChild(); // Param types for (Node n : funType.getParameters()) { // Bail out if the paramNode is not there. if (paramNode == null) { break; } sb.append(" * "); appendAnnotation(sb, "param", getParameterNodeJSDocType(n)); sb.append(" ") .append(paramNode.getString()) .append("\n"); paramNode = paramNode.getNext(); } } // Return type JSType retType = funType.getReturnType(); if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) { sb.append(" * "); appendAnnotation(sb, "return", retType.toAnnotationString()); sb.append("\n"); } // Constructor/interface if (funType.isConstructor() || funType.isInterface()) { FunctionType superConstructor = funType.getSuperClassConstructor(); if (superConstructor != null) { ObjectType superInstance = funType.getSuperClassConstructor().getInstanceType(); if (!superInstance.toString().equals("Object")) { sb.append(" * "); appendAnnotation(sb, "extends", superInstance.toAnnotationString()); sb.append("\n"); } } if (funType.isInterface()) { for (ObjectType interfaceType : funType.getExtendedInterfaces()) { sb.append(" * "); appendAnnotation(sb, "extends", interfaceType.toAnnotationString()); sb.append("\n"); } } // Avoid duplicates, add implemented type to a set first Set<String> interfaces = Sets.newTreeSet(); for (ObjectType interfaze : funType.getImplementedInterfaces()) { interfaces.add(interfaze.toAnnotationString()); } for (String interfaze : interfaces) { sb.append(" * "); appendAnnotation(sb, "implements", interfaze); sb.append("\n"); } if (funType.isConstructor()) { sb.append(" * @constructor\n"); } else if (funType.isInterface()) { sb.append(" * @interface\n"); } } if (fnNode != null && fnNode.getBooleanProp(Node.IS_DISPATCHER)) { sb.append(" * @javadispatch\n"); } sb.append(" */\n"); return sb.toString(); } private void appendAnnotation(StringBuilder sb, String name, String type) { sb.append("@").append(name).append("

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> parent) { if (parent.isAssign()) { Node lValue = parent.getFirstChild(); if (NodeUtil.isGet(lValue)) { // We have an assignment of the form "a.b = ...". JSType lValueType = lValue.getJSType(); if (lValueType != null && lValueType.isNominalConstructor()) { // If a.b is a constructor, then everything in this function // belongs to the "a.b" type. return (lValueType.toMaybeFunctionType()).getInstanceType(); } else { // If a.b is not a constructor, then treat this as a method // of whatever type is on "a". return normalizeClassType(lValue.getFirstChild().getJSType()); } } else { // We have an assignment of the form "a = ...", so pull the // type off the "a". return normalizeClassType(lValue.getJSType()); } } else if (NodeUtil.isFunctionDeclaration(n) || parent.isName()) { return normalizeClassType(n.getJSType()); } return null; } /** * Normalize the type of a constructor, its instance, and its prototype * all down to the same type (the instance type). */ private JSType normalizeClassType(JSType type) { if (type == null || type.isUnknownType()) { return type; } else if (type.isNominalConstructor()) { return (type.toMaybeFunctionType()).getInstanceType(); } else if (type.isFunctionPrototypeType()) { FunctionType owner = ((ObjectType) type).getOwnerFunction(); if (owner.isConstructor()) { return owner.getInstanceType(); } } return type; } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: checkNameDeprecation(t, n, parent); checkNameVisibility(t, n, parent); break; case Token.GETPROP: checkPropertyDeprecation(t, n, parent); checkPropertyVisibility(t, n, parent); checkConstantProperty(t, n); break; case Token.NEW: checkConstructorDeprecation(t, n, parent); break; } } /** * Checks the given NEW node to ensure that access restrictions are obeyed. */ private void checkConstructorDeprecation(NodeTraversal t, Node n, Node parent) { JSType type = n.getJSType(); if (type != null) { String deprecationInfo = getTypeDeprecationInfo(type); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_CLASS_REASON, type.toString(), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_CLASS, type.toString())); } } } } /** * Checks the given NAME node to ensure that access restrictions are obeyed. */ private

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> The getprop node. */ private void checkConstantProperty(NodeTraversal t, Node getprop) { // Check whether the property is modified Node parent = getprop.getParent(); boolean isDelete = parent.isDelProp(); if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop) && !parent.isInc() && !parent.isDec() && !isDelete) { return; } ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName); // Check whether constant properties are reassigned if (isConstant) { if (isDelete) { compiler.report( t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName)); return; } ObjectType oType = objectType; while (oType != null) { if (oType.hasReferenceName()) { if (initializedConstantProperties.containsEntry( oType.getReferenceName(), propertyName)) { compiler.report( t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE, propertyName)); break; } } oType = oType.getImplicitPrototype(); } Preconditions.checkState(objectType.hasReferenceName()); initializedConstantProperties.put(objectType.getReferenceName(), propertyName); // Add the prototype when we're looking at an instance object if (objectType.isInstanceType()) { ObjectType prototype = objectType.getImplicitPrototype(); if (prototype != null) { if (prototype.hasProperty(propertyName) && prototype.hasReferenceName()) { initializedConstantProperties.put(prototype.getReferenceName(), propertyName); } } } } } /** * Determines whether the given property is visible in the current context. * @param t The current traversal. * @param getprop The getprop node. */ private void checkPropertyVisibility(NodeTraversal t, Node getprop, Node parent) { ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); if (objectType != null) { // Is this a normal property access, or are we trying to override // an existing property? boolean isOverride = parent.getJSDocInfo() != null && parent.isAssign() && parent.getFirstChild() == getprop; // Find the lowest property defined on a class with visibility // information. if (isOverride) { objectType = objectType.getImplicitPrototype(); } JSDocInfo docInfo = null; for (; objectType != null; objectType = objectType.getImplicitPrototype()) { docInfo = objectType.getOwnPropertyJSDocInfo(propertyName); if (docInfo != null && docInfo.getVisibility() != Visibility.INHERITED) { break; } } if (objectType == null) { // We couldn't find a visibility modifier; assume it's public. return; } String referenceSource = getprop.getSourceFileName(); String definingSource = docInfo.getSource

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>precationWarning( NodeTraversal t, Node n, Node parent) { // In the global scope, there are only two kinds of accesses that should // be flagged for warnings: // 1) Calls of deprecated functions and methods. // 2) Instantiations of deprecated classes. // For now, we just let everything else by. if (t.inGlobalScope()) { if (!((parent.isCall() && parent.getFirstChild() == n) || n.isNew())) { return false; } } // We can always assign to a deprecated property, to keep it up to date. if (n.isGetProp() && n == parent.getFirstChild() && NodeUtil.isAssignmentOp(parent)) { return false; } return !canAccessDeprecatedTypes(t); } /** * Returns whether it's currently OK to access deprecated names and * properties. * * There are 3 exceptions when we're allowed to use a deprecated * type or property: * 1) When we're in a deprecated function. * 2) When we're in a deprecated class. * 3) When we're in a static method of a deprecated class. */ private boolean canAccessDeprecatedTypes(NodeTraversal t) { Node scopeRoot = t.getScopeRoot(); Node scopeRootParent = scopeRoot.getParent(); return // Case #1 (deprecatedDepth > 0) || // Case #2 (getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) || // Case #3 (scopeRootParent != null && scopeRootParent.isAssign() && getTypeDeprecationInfo( getClassOfMethod(scopeRoot, scopeRootParent)) != null); } /** * Returns whether this is a function node annotated as deprecated. */ private static boolean isDeprecatedFunction(Node n, Node parent) { if (n.isFunction()) { JSType type = n.getJSType(); if (type != null) { return getTypeDeprecationInfo(type) != null; } } return false; } /** * Returns the deprecation reason for the type if it is marked * as being deprecated. Returns empty string if the type is deprecated * but no reason was given. Returns null if the type is not deprecated. */ private static String getTypeDeprecationInfo(JSType type) { if (type == null) { return null; } JSDocInfo info = type.getJSDocInfo(); if (info != null && info.isDeprecated()) { if (info.getDeprecationReason() != null) { return info.getDeprecationReason(); } return ""; } ObjectType objType = ObjectType.cast(type); if (objType != null) { ObjectType implicitProto = objType.getImplicitPrototype(); if (implicitProto != null) { return getTypeDeprecationInfo(implicitProto); } } return null; } /** * Returns if a property is declared constant. */ private static boolean isPropertyDeclaredConstant( ObjectType objectType, String prop) { for (; // Only objects with reference names can have constant properties. objectType != null && objectType.hasReferenceName(); objectType =

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>FlowGraph.Branch>( controlFlowGraph, new ReachablePredicate()).compute( controlFlowGraph.getEntry().getValue()); } @Override public void exitScope(NodeTraversal t) { } @Override public void visit(NodeTraversal t, Node n, Node parent) { } private final class ReachablePredicate implements Predicate<EdgeTuple<Node, ControlFlowGraph.Branch>> { @Override public boolean apply(EdgeTuple<Node, Branch> input) { Branch branch = input.edge; if (!branch.isConditional()) { return true; } Node predecessor = input.sourceNode; Node condition = NodeUtil.getConditionExpression(predecessor); // TODO(user): Handle more complicated expression like true == true, // etc.... if (condition != null) { TernaryValue val = NodeUtil.getImpureBooleanValue(condition); if (val != TernaryValue.UNKNOWN) { return val.toBoolean(true) == (branch == Branch.ON_TRUE); } } return true; } } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Map; /** * Filters warnings based on in-code {@code @suppress} annotations. * @author nicksantos@google.com (Nick Santos) */ class SuppressDocWarningsGuard extends WarningsGuard { private static final long serialVersionUID = 1L; /** Warnings guards for each suppressible warnings group, indexed by name. */ private final Map<String, DiagnosticGroupWarningsGuard> suppressors = Maps.newHashMap(); /** * The suppressible groups, indexed by name. */ SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) { for (Map.Entry<String, DiagnosticGroup> entry : suppressibleGroups.entrySet()) { suppressors.put( entry.getKey(), new DiagnosticGroupWarningsGuard( entry.getValue(), CheckLevel.OFF)); } } @Override public CheckLevel level(JSError error) { Node node = error.node; if (node != null) { for (Node current = node; current != null; current = current.getParent()) { int type = current.getType(); JSDocInfo info = null; // We only care about function annotations at the FUNCTION and SCRIPT // level. Otherwise, the @suppress annotation has an implicit // dependency on the exact structure of our AST, and that seems like // a bad idea. if (type == Token.FUNCTION) { info = NodeUtil.getFunctionJSDocInfo(current); } else if (type == Token.SCRIPT) { info = current.getJSDocInfo(); } else if (type == Token.ASSIGN) { Node rhs = current.getLastChild(); if (rhs.isFunction()) { info = NodeUtil.getFunctionJSDocInfo(rhs); } } if (info != null) { for (String suppressor : info.getSuppressions()) { WarningsGuard guard = suppressors.get(suppressor); // Some @suppress tags are for other tools, and // may not have a warnings guard. if (guard != null) { CheckLevel newLevel = guard.level(error); if (newLevel != null) { return newLevel; } } } } } } return null;

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.StaticSourceFile; import com.google.javascript.rhino.jstype.TernaryValue; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * NodeUtil contains utilities that get properties from the Node object. * */ public final class NodeUtil { static final long MAX_POSITIVE_INTEGER_NUMBER = (long)Math.pow(2, 53); final static String JSC_PROPERTY_NAME_FN = "JSCompiler_renameProperty"; // TODO(user): Eliminate this class and make all of the static methods // instance methods of com.google.javascript.rhino.Node. /** the set of builtin constructors that don't have side effects. */ private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS = new HashSet<String>(Arrays.asList( "Array", "Date", "Error", "Object", "RegExp", "XMLHttpRequest")); // Utility class; do not instantiate. private NodeUtil() {} /** * Gets the boolean value of a node that represents a expression. This method * effectively emulates the <code>Boolean()</code> JavaScript cast function. * Note: unlike getBooleanValue this function does not return UNKNOWN * for expressions with side-effects. */ static TernaryValue getImpureBooleanValue(Node n) { switch (n.getType()) { case Token.ASSIGN: case Token.COMMA: // For ASSIGN and COMMA the value is the value of the

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> literals as well. switch (n.getType()) { case Token.STRING: case Token.STRING_KEY: return n.getString(); case Token.NAME: String name = n.getString(); if ("undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name)) { return name; } break; case Token.NUMBER: return getStringValue(n.getDouble()); case Token.FALSE: return "false"; case Token.TRUE: return "true"; case Token.NULL: return "null"; case Token.VOID: return "undefined"; case Token.NOT: TernaryValue child = getPureBooleanValue(n.getFirstChild()); if (child != TernaryValue.UNKNOWN) { return child.toBoolean(true) ? "false" : "true"; // reversed. } break; case Token.ARRAYLIT: return arrayToString(n); case Token.OBJECTLIT: return "[object Object]"; } return null; } static String getStringValue(double value) { long longValue = (long) value; // Return "1" instead of "1.0" if (longValue == value) { return Long.toString(longValue); } else { return Double.toString(value); } } /** * When converting arrays to string using Array.prototype.toString or * Array.prototype.join, the rules for conversion to String are different * than converting each element individually. Specifically, "null" and * "undefined" are converted to an empty string. * @param n A node that is a member of an Array. * @return The string representation. */ static String getArrayElementStringValue(Node n) { return (NodeUtil.isNullOrUndefined(n) || n.isEmpty()) ? "" : getStringValue(n); } static String arrayToString(Node literal) { Node first = literal.getFirstChild(); StringBuilder result = new StringBuilder(); int nextSlot = 0; int nextSkipSlot = 0; for (Node n = first; n != null; n = n.getNext()) { String childValue = getArrayElementStringValue(n); if (childValue == null) { return null; } if (n != first) { result.append(','); } result.append(childValue); nextSlot++; } return result.toString(); } /** * Gets the value of a node as a Number, or null if it cannot be converted. * When it returns a non-null Double, this method effectively emulates the * <code>Number()</code> JavaScript cast function. */ static Double getNumberValue(Node n) { switch (n.getType()) { case Token.TRUE: return 1.0; case Token.FALSE: case Token.NULL: return 0.0; case Token.NUMBER: return n.getDouble(); case Token.VOID: if (mayHaveSideEffects(n.getFirstChild())) { return null; } else { return Double.NaN; } case Token.NAME: // Check for known constants String name = n.getString(); if (name

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>); if (name != null) { return name; } // Check for the form { 'x' : function() { } } Node parent = n.getParent(); switch (parent.getType()) { case Token.SETTER_DEF: case Token.GETTER_DEF: case Token.STRING_KEY: // Return the name of the literal's key. return parent.getString(); case Token.NUMBER: return getStringValue(parent); } return null; } /** * Returns true if this is an immutable value. */ static boolean isImmutableValue(Node n) { switch (n.getType()) { case Token.STRING: case Token.NUMBER: case Token.NULL: case Token.TRUE: case Token.FALSE: return true; case Token.NOT: return isImmutableValue(n.getFirstChild()); case Token.VOID: case Token.NEG: return isImmutableValue(n.getFirstChild()); case Token.NAME: String name = n.getString(); // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return "undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name); } return false; } /** * Returns true if the operator on this node is symmetric */ public static boolean isSymmetricOperation(Node n) { switch (n.getType()) { case Token.EQ: // equal case Token.NE: // not equal case Token.SHEQ: // exactly equal case Token.SHNE: // exactly not equal case Token.MUL: // multiply, unlike add it only works on numbers // or results NaN if any of the operators is not a number return true; } return false; } /** * Returns true if the operator on this node is relational. * the returned set does not include the equalities. */ public static boolean isRelationalOperation(Node n) { switch (n.getType()) { case Token.GT: // equal case Token.GE: // not equal case Token.LT: // exactly equal case Token.LE: // exactly not equal return true; } return false; } /** * Returns the inverse of an operator if it is invertible. * ex. '>' ==> '<' */ public static int getInverseOperator(int type) { switch (type) { case Token.GT: return Token.LT; case Token.LT: return Token.GT; case Token.GE: return Token.LE; case Token.LE: return Token.GE; } return Token.ERROR; } /** * Returns true if this is a literal value. We define a literal value * as any node that evaluates to the same thing regardless of when or * where it is evaluated. So /xyz/ and [3, 5] are literals, but * the name a is not. * * Function literals do not meet this definition, because they * lexically capture variables. For example, if you have * <code> * function() { return a; } * </code> * If it is evaluated in a

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> different scope, then it * captures a different variable. Even if the function did not read * any captured variables directly, it would still fail this definition, * because it affects the lifecycle of variables in the enclosing scope. * * However, a function literal with respect to a particular scope is * a literal. * * @param includeFunctions If true, all function expressions will be * treated as literals. */ static boolean isLiteralValue(Node n, boolean includeFunctions) { switch (n.getType()) { case Token.ARRAYLIT: for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if ((!child.isEmpty()) && !isLiteralValue(child, includeFunctions)) { return false; } } return true; case Token.REGEXP: // Return true only if all children are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child, includeFunctions)) { return false; } } return true; case Token.OBJECTLIT: // Return true only if all values are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child.getFirstChild(), includeFunctions)) { return false; } } return true; case Token.FUNCTION: return includeFunctions && !NodeUtil.isFunctionDeclaration(n); default: return isImmutableValue(n); } } /** * Determines whether the given value may be assigned to a define. * * @param val The value being assigned. * @param defines The list of names of existing defines. */ static boolean isValidDefineValue(Node val, Set<String> defines) { switch (val.getType()) { case Token.STRING: case Token.NUMBER: case Token.TRUE: case Token.FALSE: return true; // Binary operators are only valid if both children are valid. case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return isValidDefineValue(val.getFirstChild(), defines) && isValidDefineValue(val.getLastChild(), defines); // Unary operators are valid if the child is valid. case Token.NOT: case Token.NEG: case Token.POS: return isValidDefineValue(val.getFirstChild(), defines); // Names are valid if and only if they are defines themselves. case Token.NAME: case Token.GETPROP: if (val.isQualifiedName()) { return defines.contains(val.getQualifiedName()); } } return false; } /** * Returns whether this a BLOCK node with no children. *

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>.isCall()) { throw new IllegalStateException( "Expected CALL node, got " + Token.name(callNode.getType())); } if (callNode.isNoSideEffectsCall()) { return false; } Node nameNode = callNode.getFirstChild(); // Built-in functions with no side effects. if (nameNode.isName()) { String name = nameNode.getString(); if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) { return false; } } else if (nameNode.isGetProp()) { if (callNode.hasOneChild() && OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains( nameNode.getLastChild().getString())) { return false; } if (callNode.isOnlyModifiesThisCall() && evaluatesToLocalValue(nameNode.getFirstChild())) { return false; } // Math.floor has no side-effects. // TODO(nicksantos): This is a terrible terrible hack, until // I create a definitionProvider that understands namespacing. if (nameNode.getFirstChild().isName()) { if ("Math.floor".equals(nameNode.getQualifiedName())) { return false; } } if (compiler != null && !compiler.hasRegExpGlobalReferences()) { if (nameNode.getFirstChild().isRegExp() && REGEXP_METHODS.contains(nameNode.getLastChild().getString())) { return false; } else if (nameNode.getFirstChild().isString() && STRING_REGEXP_METHODS.contains( nameNode.getLastChild().getString())) { Node param = nameNode.getNext(); if (param != null && (param.isString() || param.isRegExp())) return false; } } } return true; } /** * @return Whether the call has a local result. */ static boolean callHasLocalResult(Node n) { Preconditions.checkState(n.isCall()); return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0; } /** * @return Whether the new has a local result. */ static boolean newHasLocalResult(Node n) { Preconditions.checkState(n.isNew()); return n.isOnlyModifiesThisCall(); } /** * Returns true if the current node's type implies side effects. * * This is a non-recursive version of the may have side effects * check; used to check wherever the current node's type is one of * the reason's why a subtree has side effects. */ static boolean nodeTypeMayHaveSideEffects(Node n) { return nodeTypeMayHaveSideEffects(n, null); } static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) { if (isAssignmentOp(n)) { return true; } switch(n.getType()) { case Token.DELPROP: case Token.DEC: case Token.INC: case Token.THROW: return true; case Token.CALL: return NodeUtil.functionCallHasSideEffects(n, compiler); case Token.NEW: return NodeUtil.constructorCallHas

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> GETELEM node? */ static boolean isGet(Node n) { return n.isGetProp() || n.isGetElem(); } /** * Is this node the name of a variable being declared? * * @param n The node * @return True if {@code n} is NAME and {@code parent} is VAR */ static boolean isVarDeclaration(Node n) { // There is no need to verify that parent != null because a NAME node // always has a parent in a valid parse tree. return n.isName() && n.getParent().isVar(); } /** * For an assignment or variable declaration get the assigned value. * @return The value node representing the new value. */ static Node getAssignedValue(Node n) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); if (parent.isVar()) { return n.getFirstChild(); } else if (parent.isAssign() && parent.getFirstChild() == n) { return n.getNext(); } else { return null; } } /** * Is this node an assignment expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is ASSIGN */ static boolean isExprAssign(Node n) { return n.isExprResult() && n.getFirstChild().isAssign(); } /** * Is this node a call expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is CALL */ static boolean isExprCall(Node n) { return n.isExprResult() && n.getFirstChild().isCall(); } /** * @return Whether the node represents a FOR-IN loop. */ static boolean isForIn(Node n) { return n.isFor() && n.getChildCount() == 3; } /** * Determines whether the given node is a FOR, DO, or WHILE node. */ static boolean isLoopStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * @param n The node to inspect. * @return If the node, is a FOR, WHILE, or DO, it returns the node for * the code BLOCK, null otherwise. */ static Node getLoopCodeBlock(Node n) { switch (n.getType()) { case Token.FOR: case Token.WHILE: return n.getLastChild(); case Token.DO: return n.getFirstChild(); default: return null; } } /** * @return Whether the specified node has a loop parent that * is within the current scope. */ static boolean isWithinLoop(Node n) { for (Node parent : n.getAncestors()) { if (NodeUtil.isLoopStructure(parent)) { return true; } if (parent.isFunction()) { break; } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> } /** Whether the node is part of a switch statement. */ static boolean isSwitchCase(Node n) { return n.isCase() || n.isDefaultCase(); } /** * @return Whether the name is a reference to a variable, function or * function parameter (not a label or a empty function expression name). */ static boolean isReferenceName(Node n) { return n.isName() && !n.getString().isEmpty(); } /** Whether the child node is the FINALLY block of a try. */ static boolean isTryFinallyNode(Node parent, Node child) { return parent.isTry() && parent.getChildCount() == 3 && child == parent.getLastChild(); } /** Whether the node is a CATCH container BLOCK. */ static boolean isTryCatchNodeContainer(Node n) { Node parent = n.getParent(); return parent.isTry() && parent.getFirstChild().getNext() == n; } /** Safely remove children while maintaining a valid node structure. */ static void removeChild(Node parent, Node node) { if (isTryFinallyNode(parent, node)) { if (NodeUtil.hasCatchHandler(getCatchBlock(parent))) { // A finally can only be removed if there is a catch. parent.removeChild(node); } else { // Otherwise, only its children can be removed. node.detachChildren(); } } else if (node.isCatch()) { // The CATCH can can only be removed if there is a finally clause. Node tryNode = node.getParent().getParent(); Preconditions.checkState(NodeUtil.hasFinally(tryNode)); node.detachFromParent(); } else if (isTryCatchNodeContainer(node)) { // The container node itself can't be removed, but the contained CATCH // can if there is a 'finally' clause Node tryNode = node.getParent(); Preconditions.checkState(NodeUtil.hasFinally(tryNode)); node.detachChildren(); } else if (node.isBlock()) { // Simply empty the block. This maintains source location and // "synthetic"-ness. node.detachChildren(); } else if (isStatementBlock(parent) || isSwitchCase(node)) { // A statement in a block can simply be removed. parent.removeChild(node); } else if (parent.isVar()) { if (parent.hasMoreThanOneChild()) { parent.removeChild(node); } else { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // This would leave an empty VAR, remove the VAR itself. removeChild(parent.getParent(), parent); } } else if (parent.isLabel() && node == parent.getLastChild()) { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // A LABEL without children can not be referred to, remove it. removeChild(parent.getParent(), parent); } else if (parent.isFor() && parent.getChildCount() == 4) { // Only Token.FOR can have an Token.EMPTY other control structure // need something for the condition. Others need to be replaced // or

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> the structure removed. parent.replaceChild(node, IR.empty()); } else { throw new IllegalStateException("Invalid attempt to remove node: " + node.toString() + " of "+ parent.toString()); } } /** * Add a finally block if one does not exist. */ static void maybeAddFinally(Node tryNode) { Preconditions.checkState(tryNode.isTry()); if (!NodeUtil.hasFinally(tryNode)) { tryNode.addChildrenToBack(IR.block().srcref(tryNode)); } } /** * Merge a block with its parent block. * @return Whether the block was removed. */ static boolean tryMergeBlock(Node block) { Preconditions.checkState(block.isBlock()); Node parent = block.getParent(); // Try to remove the block if its parent is a block/script or if its // parent is label and it has exactly one child. if (isStatementBlock(parent)) { Node previous = block; while (block.hasChildren()) { Node child = block.removeFirstChild(); parent.addChildAfter(child, previous); previous = child; } parent.removeChild(block); return true; } else { return false; } } /** * @param node A node * @return Whether the call is a NEW or CALL node. */ static boolean isCallOrNew(Node node) { return node.isCall() || node.isNew(); } /** * Return a BLOCK node for the given FUNCTION node. */ static Node getFunctionBody(Node fn) { Preconditions.checkArgument(fn.isFunction()); return fn.getLastChild(); } /** * Is this node or any of its children a CALL? */ static boolean containsCall(Node n) { return containsType(n, Token.CALL); } /** * Is this node a function declaration? A function declaration is a function * that has a name that is added to the current scope (i.e. a function that * is not part of a expression; see {@link #isFunctionExpression}). */ static boolean isFunctionDeclaration(Node n) { return n.isFunction() && isStatement(n); } /** * Is this node a hoisted function declaration? A function declaration in the * scope root is hoisted to the top of the scope. * See {@link #isFunctionDeclaration}). */ static boolean isHoistedFunctionDeclaration(Node n) { return isFunctionDeclaration(n) && (n.getParent().isScript() || n.getParent().getParent().isFunction()); } /** * Is a FUNCTION node an function expression? An function expression is one * that has either no name or a name that is not added to the current scope. * * <p>Some examples of function expressions: * <pre> * (function () {}) * (function f() {})() * [ function f() {} ] * var f = function f() {}; * for (function f() {};;) {} * </pre> * * <p>Some examples of functions that are <em>not</em> expressions: * <pre> * function f() {} * if (x); else

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> is used only as an L-value. * * @param n The node * @param parent Parent of the node * @return True if n is the left hand of an assign */ static boolean isVarOrSimpleAssignLhs(Node n, Node parent) { return (parent.isAssign() && parent.getFirstChild() == n) || parent.isVar(); } /** * Determines whether this node is used as an L-value. Notice that sometimes * names are used as both L-values and R-values. * * We treat "var x;" as a pseudo-L-value, which kind of makes sense if you * treat it as "assignment to 'undefined' at the top of the scope". But if * we're honest with ourselves, it doesn't make sense, and we only do this * because it makes sense to treat this as syntactically similar to * "var x = 0;". * * @param n The node * @return True if n is an L-value. */ static boolean isLValue(Node n) { Preconditions.checkArgument(n.isName() || n.isGetProp() || n.isGetElem()); Node parent = n.getParent(); return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) || (NodeUtil.isForIn(parent) && parent.getFirstChild() == n) || parent.isVar() || (parent.isFunction() && parent.getFirstChild() == n) || parent.isDec() || parent.isInc() || parent.isParamList() || parent.isCatch(); } /** * Determines whether a node represents an object literal key * (e.g. key1 in {key1: value1, key2: value2}). * * @param node A node * @param parent The node's parent */ static boolean isObjectLitKey(Node node, Node parent) { switch (node.getType()) { case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: return true; } return false; } /** * Get the name of an object literal key. * * @param key A node */ static String getObjectLitKeyName(Node key) { switch (key.getType()) { case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: return key.getString(); } throw new IllegalStateException("Unexpected node type: " + key); } /** * @param key A OBJECTLIT key node. * @return The type expected when using the key. */ static JSType getObjectLitKeyTypeFromValueType(Node key, JSType valueType) { if (valueType != null) { switch (key.getType()) { case Token.GETTER_DEF: // GET must always return a function type. if (valueType.isFunctionType()) { FunctionType fntype = valueType.toMaybeFunctionType(); valueType = fntype.getReturnType(); } else { return null; } break; case Token.SETTER_DEF: if (valueType.isFunctionType())

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>Return true here if the variable includes $$constant in its name. * </ol> * * @param node A NAME or STRING node * @return True if the variable is constant */ static boolean isConstantName(Node node) { return node.getBooleanProp(Node.IS_CONSTANT_NAME); } /** Whether the given name is constant by coding convention. */ static boolean isConstantByConvention( CodingConvention convention, Node node, Node parent) { String name = node.getString(); if (parent.isGetProp() && node == parent.getLastChild()) { return convention.isConstantKey(name); } else if (isObjectLitKey(node, parent)) { return convention.isConstantKey(name); } else { return convention.isConstant(name); } } /** * Get the JSDocInfo for a function. */ public static JSDocInfo getFunctionJSDocInfo(Node n) { Preconditions.checkState(n.isFunction()); JSDocInfo fnInfo = n.getJSDocInfo(); if (fnInfo == null && NodeUtil.isFunctionExpression(n)) { // Look for the info on other nodes. Node parent = n.getParent(); if (parent.isAssign()) { // on ASSIGNs fnInfo = parent.getJSDocInfo(); } else if (parent.isName()) { // on var NAME = function() { ... }; fnInfo = parent.getParent().getJSDocInfo(); } } return fnInfo; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ public static String getSourceName(Node n) { String sourceName = null; while (sourceName == null && n != null) { sourceName = n.getSourceFileName(); n = n.getParent(); } return sourceName; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ public static StaticSourceFile getSourceFile(Node n) { StaticSourceFile sourceName = null; while (sourceName == null && n != null) { sourceName = n.getStaticSourceFile(); n = n.getParent(); } return sourceName; } /** * @param n The node. * @return The InputId property on the node or its ancestors. */ public static InputId getInputId(Node n) { while (n != null && !n.isScript()) { n = n.getParent(); } return (n != null && n.isScript()) ? n.getInputId() : null; } /** * A new CALL node with the "FREE_CALL" set based on call target. */ static Node newCallNode(Node callTarget, Node... parameters) { boolean isFreeCall = !isGet(callTarget); Node call = IR.call(callTarget); call.putBooleanProp(Node.FREE_CALL, isFreeCall); for (Node parameter : parameters) { call.addChildToBack(parameter); } return call; } /** * @return Whether the node is known to be a

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> value that is not referenced * elsewhere. */ static boolean evaluatesToLocalValue(Node value) { return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse()); } /** * @param locals A predicate to apply to unknown local values. * @return Whether the node is known to be a value that is not a reference * outside the expression scope. */ static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) { switch (value.getType()) { case Token.ASSIGN: // A result that is aliased by a non-local name, is the effectively the // same as returning a non-local name, but this doesn't matter if the // value is immutable. return NodeUtil.isImmutableValue(value.getLastChild()) || (locals.apply(value) && evaluatesToLocalValue(value.getLastChild(), locals)); case Token.COMMA: return evaluatesToLocalValue(value.getLastChild(), locals); case Token.AND: case Token.OR: return evaluatesToLocalValue(value.getFirstChild(), locals) && evaluatesToLocalValue(value.getLastChild(), locals); case Token.HOOK: return evaluatesToLocalValue(value.getFirstChild().getNext(), locals) && evaluatesToLocalValue(value.getLastChild(), locals); case Token.INC: case Token.DEC: if (value.getBooleanProp(Node.INCRDECR_PROP)) { return evaluatesToLocalValue(value.getFirstChild(), locals); } else { return true; } case Token.THIS: return locals.apply(value); case Token.NAME: return isImmutableValue(value) || locals.apply(value); case Token.GETELEM: case Token.GETPROP: // There is no information about the locality of object properties. return locals.apply(value); case Token.CALL: return callHasLocalResult(value) || isToStringMethodCall(value) || locals.apply(value); case Token.NEW: return newHasLocalResult(value) || locals.apply(value); case Token.FUNCTION: case Token.REGEXP: case Token.ARRAYLIT: case Token.OBJECTLIT: // Literals objects with non-literal children are allowed. return true; case Token.DELPROP: case Token.IN: // TODO(johnlenz): should IN operator be included in #isSimpleOperator? return true; default: // Other op force a local value: // x = '' + g (x is now an local string) // x -= g (x is now an local number) if (isAssignmentOp(value) || isSimpleOperator(value) || isImmutableValue(value)) { return true; } throw new IllegalStateException( "Unexpected expression node" + value + "\n parent:" + value.getParent()); } } /** * Given the first sibling, this returns the nth * sibling or null if no such sibling exists. * This is like "getChildAtIndex" but returns null for non-existent indexes. */ private static Node getNthSibling(Node first, int index) { Node sibling = first; while (index != 0 && sibling != null) { sibling = sibling.

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> Token.ASSIGN: return n.getNext(); case Token.VAR: return n.getFirstChild(); case Token.FUNCTION: return parent; } return null; } /** Get the owner of the given l-value node. */ static Node getBestLValueOwner(@Nullable Node lValue) { if (lValue == null || lValue.getParent() == null) { return null; } if (isObjectLitKey(lValue, lValue.getParent())) { return getBestLValue(lValue.getParent()); } else if (isGet(lValue)) { return lValue.getFirstChild(); } return null; } /** Get the name of the given l-value node. */ static String getBestLValueName(@Nullable Node lValue) { if (lValue == null || lValue.getParent() == null) { return null; } if (isObjectLitKey(lValue, lValue.getParent())) { Node owner = getBestLValue(lValue.getParent()); if (owner != null) { String ownerName = getBestLValueName(owner); if (ownerName != null) { return ownerName + "." + getObjectLitKeyName(lValue); } } return null; } return lValue.getQualifiedName(); } /** * @returns false iff the result of the expression is not consumed. */ static boolean isExpressionResultUsed(Node expr) { // TODO(johnlenz): consider sharing some code with trySimpleUnusedResult. Node parent = expr.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.EXPR_RESULT: return false; case Token.HOOK: case Token.AND: case Token.OR: return (expr == parent.getFirstChild()) ? true : isExpressionResultUsed(parent); case Token.COMMA: Node gramps = parent.getParent(); if (gramps.isCall() && parent == gramps.getFirstChild()) { // Semantically, a direct call to eval is different from an indirect // call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first // expression to a comma to be a no-op if it's used to indirect // an eval. This we pretend that this is "used". if (expr == parent.getFirstChild() && parent.getChildCount() == 2 && expr.getNext().isName() && "eval".equals(expr.getNext().getString())) { return true; } } return (expr == parent.getFirstChild()) ? false : isExpressionResultUsed(parent); case Token.FOR: if (!NodeUtil.isForIn(parent)) { // Only an expression whose result is in the condition part of the // expression is used. return (parent.getChildAtIndex(1) == expr); } break; } return true; } /** * @param n The expression to check. * @return Whether the expression is unconditionally executed only once in the * containing execution scope. */ static boolean isExecutedExactlyOnce(Node n) { inspect: do { Node parent =

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> n.getParent(); switch (parent.getType()) { case Token.IF: case Token.HOOK: case Token.AND: case Token.OR: if (parent.getFirstChild() != n) { return false; } // other ancestors may be conditional continue inspect; case Token.FOR: if (NodeUtil.isForIn(parent)) { if (parent.getChildAtIndex(1) != n) { return false; } } else { if (parent.getFirstChild() != n) { return false; } } // other ancestors may be conditional continue inspect; case Token.WHILE: case Token.DO: return false; case Token.TRY: // Consider all code under a try/catch to be conditionally executed. if (!hasFinally(parent) || parent.getLastChild() != n) { return false; } continue inspect; case Token.CASE: case Token.DEFAULT_CASE: return false; case Token.SCRIPT: case Token.FUNCTION: // Done, we've reached the scope root. break inspect; } } while ((n = n.getParent()) != null); return true; } static Node booleanNode(boolean value) { return value ? IR.trueNode() : IR.falseNode(); } static Node numberNode(double value, Node srcref) { Node result; if (Double.isNaN(value)) { result = IR.name("NaN"); } else if (value == Double.POSITIVE_INFINITY) { result = IR.name("Infinity"); } else if (value == Double.NEGATIVE_INFINITY) { result = IR.neg(IR.name("Infinity")); } else { result = IR.number(value); } if (srcref != null) { result.srcrefTree(srcref); } return result; } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.Map; /** * CodeGenerator generates codes from a parse tree, sending it to the specified * CodeConsumer. * */ class CodeGenerator { // A memoizer for formatting strings as JS strings. private final Map<String, String> ESCAPED_JS_STRINGS = Maps.newHashMap(); private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private final CodeConsumer cc; private final CharsetEncoder outputCharsetEncoder; CodeGenerator( CodeConsumer consumer, Charset outputCharset) { cc = consumer; if (outputCharset == null || outputCharset == Charsets.US_ASCII) { // If we want our default (pretending to be UTF-8, but escaping anything // outside of straight ASCII), then don't use the encoder, but // just special-case the code. This keeps the normal path through // the code identical to how it's been for years. this.outputCharsetEncoder = null; } else { this.outputCharsetEncoder = outputCharset.newEncoder(); } } CodeGenerator(CodeConsumer consumer) { this(consumer, null); } /** * Insert a ECMASCRIPT 5 strict annotation. */ public void tagAsStrict() { add("'use strict';"); } void add(String str) { cc.add(str); } private void addIdentifier(String identifier) { cc.addIdentifier(identifierEscape(identifier)); } void add(Node n) { add(n, Context.OTHER); } void add(Node n, Context context) { if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); // Handle associativity. // e.g. if the parse tree is a * (b * c), // we can simply generate a * b * c. if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { Preconditions.checkState(first.getNext().isBlock() && !first.getNext().hasMoreThanOneChild()); Preconditions.checkState(childCount >= 2 && childCount <= 3); add("try"); add(first, Context.PRESERVE_BLOCK); // second child contains the catch block, or nothing if there // isn't a catch block Node catchblock = first.getNext().getFirstChild(); if (catchblock != null) { add(catchblock); } if (childCount == 3) { add("finally"); add(last, Context.PRESERVE_BLOCK); } break; } case Token.CATCH: Preconditions.checkState(childCount == 2); add("catch("); add(first); add(")"); add(last, Context.PRESERVE_BLOCK); break; case Token.THROW: Preconditions.checkState(childCount == 1); add("throw"); add(first); // Must have a ';' after a throw statement, otherwise safari can't // parse this. cc.endStatement(true); break; case Token.RETURN: add("return"); if (childCount == 1) { add(first); } else { Preconditions.checkState(childCount == 0); } cc.endStatement(); break; case Token.VAR: if (first != null) { add("var "); addList(first, false, getContextForNoInOperator(context)); } break; case Token.LABEL_NAME: Preconditions.checkState(!n.getString().isEmpty()); addIdentifier(n.getString()); break; case Token.NAME: if (first == null || first.isEmpty())

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> { addIdentifier(n.getString()); } else { Preconditions.checkState(childCount == 1); addIdentifier(n.getString()); cc.addOp("=", true); if (first.isComma()) { addExpr(first, NodeUtil.precedence(Token.ASSIGN), Context.OTHER); } else { // Add expression, consider nearby code at lowest level of // precedence. addExpr(first, 0, getContextForNoInOperator(context)); } } break; case Token.ARRAYLIT: add("["); addArrayList(first); add("]"); break; case Token.PARAM_LIST: add("("); addList(first); add(")"); break; case Token.COMMA: Preconditions.checkState(childCount == 2); unrollBinaryOperator(n, Token.COMMA, ",", context, Context.OTHER, 0, 0); break; case Token.NUMBER: Preconditions.checkState(childCount == 0); cc.addNumber(n.getDouble()); break; case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: { // All of these unary operators are right-associative Preconditions.checkState(childCount == 1); cc.addOp(NodeUtil.opToStrNoFail(type), false); addExpr(first, NodeUtil.precedence(type), Context.OTHER); break; } case Token.NEG: { Preconditions.checkState(childCount == 1); // It's important to our sanity checker that the code // we print produces the same AST as the code we parse back. // NEG is a weird case because Rhino parses "- -2" as "2". if (n.getFirstChild().isNumber()) { cc.addNumber(-n.getFirstChild().getDouble()); } else { cc.addOp(NodeUtil.opToStrNoFail(type), false); addExpr(first, NodeUtil.precedence(type), Context.OTHER); } break; } case Token.HOOK: { Preconditions.checkState(childCount == 3); int p = NodeUtil.precedence(type); addExpr(first, p + 1, context); cc.addOp("?", true); addExpr(first.getNext(), 1, Context.OTHER); cc.addOp(":", true); addExpr(last, 1, Context.OTHER); break; } case Token.REGEXP: if (!first.isString() || !last.isString()) { throw new Error("Expected children to be strings"); } String regexp = regexpEscape(first.getString(), outputCharsetEncoder); // I only use one .add because whitespace matters if (childCount == 2) { add(regexp + last.getString()); } else { Preconditions.checkState(childCount == 1); add(regexp); } break; case Token.FUNCTION: if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } Preconditions.checkState(childCount == 3); boolean funcNeedsPare

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>ns = (context == Context.START_OF_EXPR); if (funcNeedsParens) { add("("); } add("function"); add(first); add(first.getNext()); add(last, Context.PRESERVE_BLOCK); cc.endFunction(context == Context.STATEMENT); if (funcNeedsParens) { add(")"); } break; case Token.GETTER_DEF: case Token.SETTER_DEF: Preconditions.checkState(n.getParent().isObjectLit()); Preconditions.checkState(childCount == 1); Preconditions.checkState(first.isFunction()); // Get methods are unnamed Preconditions.checkState(first.getFirstChild().getString().isEmpty()); if (type == Token.GETTER_DEF) { // Get methods have no parameters. Preconditions.checkState(!first.getChildAtIndex(1).hasChildren()); add("get "); } else { // Set methods have one parameter. Preconditions.checkState(first.getChildAtIndex(1).hasOneChild()); add("set "); } // The name is on the GET or SET node. String name = n.getString(); Node fn = first; Node parameters = fn.getChildAtIndex(1); Node body = fn.getLastChild(); // Add the property name. if (!n.isQuotedString() && TokenStream.isJSIdentifier(name) && // do not encode literally any non-literal characters that were // Unicode escaped. NodeUtil.isLatin(name)) { add(name); } else { // Determine if the string is a simple number. double d = getSimpleNumber(name); if (!Double.isNaN(d)) { cc.addNumber(d); } else { addJsString(n); } } add(parameters); add(body, Context.PRESERVE_BLOCK); break; case Token.SCRIPT: case Token.BLOCK: { if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } boolean preserveBlock = context == Context.PRESERVE_BLOCK; if (preserveBlock) { cc.beginBlock(); } boolean preferLineBreaks = type == Token.SCRIPT || (type == Token.BLOCK && !preserveBlock && n.getParent() != null && n.getParent().isScript()); for (Node c = first; c != null; c = c.getNext()) { add(c, Context.STATEMENT); // VAR doesn't include ';' since it gets used in expressions if (c.isVar()) { cc.endStatement(); } if (c.isFunction()) { cc.maybeLineBreak(); } // Prefer to break lines in between top-level statements // because top-level statements are more homogeneous. if (preferLineBreaks) { cc.notePreferredLineBreak(); } } if (preserveBlock) { cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); } break; } case Token.FOR: if (childCount == 4) { add("for("); if (first.isVar())

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> { add(first, Context.IN_FOR_INIT_CLAUSE); } else { addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE); } add(";"); add(first.getNext()); add(";"); add(first.getNext().getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } else { Preconditions.checkState(childCount == 3); add("for("); add(first); add("in"); add(first.getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } break; case Token.DO: Preconditions.checkState(childCount == 2); add("do"); addNonEmptyStatement(first, Context.OTHER, false); add("while("); add(last); add(")"); cc.endStatement(); break; case Token.WHILE: Preconditions.checkState(childCount == 2); add("while("); add(first); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); break; case Token.EMPTY: Preconditions.checkState(childCount == 0); break; case Token.GETPROP: { Preconditions.checkState( childCount == 2, "Bad GETPROP: expected 2 children, but got %s", childCount); Preconditions.checkState( last.isString(), "Bad GETPROP: RHS should be STRING"); boolean needsParens = (first.isNumber()); if (needsParens) { add("("); } addExpr(first, NodeUtil.precedence(type), context); if (needsParens) { add(")"); } add("."); addIdentifier(last.getString()); break; } case Token.GETELEM: Preconditions.checkState( childCount == 2, "Bad GETELEM: expected 2 children but got %s", childCount); addExpr(first, NodeUtil.precedence(type), context); add("["); add(first.getNext()); add("]"); break; case Token.WITH: Preconditions.checkState(childCount == 2); add("with("); add(first); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); break; case Token.INC: case Token.DEC: { Preconditions.checkState(childCount == 1); String o = type == Token.INC ? "++" : "--"; int postProp = n.getIntProp(Node.INCRDECR_PROP); // A non-zero post-prop value indicates a post inc/dec, default of zero // is a pre-inc/dec. if (postProp != 0) { addExpr(first, NodeUtil.precedence(type), context); cc.addOp(o, false); } else { cc.addOp(o, false); add(first); } break; } case Token.CALL: // We have two special cases here: //

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> 1) If the left hand side of the call is a direct reference to eval, // then it must have a DIRECT_EVAL annotation. If it does not, then // that means it was originally an indirect call to eval, and that // indirectness must be preserved. // 2) If the left hand side of the call is a property reference, // then the call must not a FREE_CALL annotation. If it does, then // that means it was originally an call without an explicit this and // that must be preserved. if (isIndirectEval(first) || n.getBooleanProp(Node.FREE_CALL) && NodeUtil.isGet(first)) { add("(0,"); addExpr(first, NodeUtil.precedence(Token.COMMA), Context.OTHER); add(")"); } else { addExpr(first, NodeUtil.precedence(type), context); } add("("); addList(first.getNext()); add(")"); break; case Token.IF: boolean hasElse = childCount == 3; boolean ambiguousElseClause = context == Context.BEFORE_DANGLING_ELSE && !hasElse; if (ambiguousElseClause) { cc.beginBlock(); } add("if("); add(first); add(")"); if (hasElse) { addNonEmptyStatement( first.getNext(), Context.BEFORE_DANGLING_ELSE, false); add("else"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } else { addNonEmptyStatement(first.getNext(), Context.OTHER, false); Preconditions.checkState(childCount == 2); } if (ambiguousElseClause) { cc.endBlock(); } break; case Token.NULL: Preconditions.checkState(childCount == 0); cc.addConstant("null"); break; case Token.THIS: Preconditions.checkState(childCount == 0); add("this"); break; case Token.FALSE: Preconditions.checkState(childCount == 0); cc.addConstant("false"); break; case Token.TRUE: Preconditions.checkState(childCount == 0); cc.addConstant("true"); break; case Token.CONTINUE: Preconditions.checkState(childCount <= 1); add("continue"); if (childCount == 1) { if (!first.isLabelName()) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(" "); add(first); } cc.endStatement(); break; case Token.DEBUGGER: Preconditions.checkState(childCount == 0); add("debugger"); cc.endStatement(); break; case Token.BREAK: Preconditions.checkState(childCount <= 1); add("break"); if (childCount == 1) { if (!first.isLabelName()) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(" "); add(first); } cc.endStatement(); break; case Token.EXPR_RESULT: Preconditions.checkState(childCount == 1);

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> add(first, Context.START_OF_EXPR); cc.endStatement(); break; case Token.NEW: add("new "); int precedence = NodeUtil.precedence(type); // If the first child contains a CALL, then claim higher precedence // to force parentheses. Otherwise, when parsed, NEW will bind to the // first viable parentheses (don't traverse into functions). if (NodeUtil.containsType( first, Token.CALL, NodeUtil.MATCH_NOT_FUNCTION)) { precedence = NodeUtil.precedence(first.getType()) + 1; } addExpr(first, precedence, Context.OTHER); // '()' is optional when no arguments are present Node next = first.getNext(); if (next != null) { add("("); addList(next); add(")"); } break; case Token.STRING_KEY: Preconditions.checkState( childCount == 1, "Object lit key must have 1 child"); addJsString(n); break; case Token.STRING: Preconditions.checkState( childCount == 0, "A string may not have children"); addJsString(n); break; case Token.DELPROP: Preconditions.checkState(childCount == 1); add("delete "); add(first); break; case Token.OBJECTLIT: { boolean needsParens = (context == Context.START_OF_EXPR); if (needsParens) { add("("); } add("{"); for (Node c = first; c != null; c = c.getNext()) { if (c != first) { cc.listSeparator(); } if (c.isGetterDef() || c.isSetterDef()) { add(c); } else { Preconditions.checkState(c.isStringKey()); String key = c.getString(); // Object literal property names don't have to be quoted if they // are not JavaScript keywords if (!c.isQuotedString() && !TokenStream.isKeyword(key) && TokenStream.isJSIdentifier(key) && // do not encode literally any non-literal characters that // were Unicode escaped. NodeUtil.isLatin(key)) { add(key); } else { // Determine if the string is a simple number. double d = getSimpleNumber(key); if (!Double.isNaN(d)) { cc.addNumber(d); } else { addExpr(c, 1, Context.OTHER); } } add(":"); addExpr(c.getFirstChild(), 1, Context.OTHER); } } add("}"); if (needsParens) { add(")"); } break; } case Token.SWITCH: add("switch("); add(first); add(")"); cc.beginBlock(); addAllSiblings(first.getNext()); cc.endBlock(context == Context.STATEMENT); break; case Token.CASE: Preconditions.checkState(childCount == 2); add("case "); add(first); addCaseBody(last); break; case Token.DEFAULT_CASE: Preconditions.checkState

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>(childCount == 1); add("default"); addCaseBody(first); break; case Token.LABEL: Preconditions.checkState(childCount == 2); if (!first.isLabelName()) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(first); add(":"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), true); break; default: throw new Error("Unknown type " + type + "\n" + n.toStringTree()); } cc.endSourceMapping(n); } /** * We could use addList recursively here, but sometimes we produce * very deeply nested operators and run out of stack space, so we * just unroll the recursion when possible. * * We assume nodes are left-recursive. */ private void unrollBinaryOperator( Node n, int op, String opStr, Context context, Context rhsContext, int leftPrecedence, int rightPrecedence) { Node firstNonOperator = n.getFirstChild(); while (firstNonOperator.getType() == op) { firstNonOperator = firstNonOperator.getFirstChild(); } addExpr(firstNonOperator, leftPrecedence, context); Node current = firstNonOperator; do { current = current.getParent(); cc.addOp(opStr, true); addExpr(current.getFirstChild().getNext(), rightPrecedence, rhsContext); } while (current != n); } static boolean isSimpleNumber(String s) { int len = s.length(); for (int index = 0; index < len; index++) { char c = s.charAt(index); if (c < '0' || c > '9') { return false; } } return len > 0 && s.charAt(0) != '0'; } static double getSimpleNumber(String s) { if (isSimpleNumber(s)) { try { long l = Long.parseLong(s); if (l < NodeUtil.MAX_POSITIVE_INTEGER_NUMBER) { return l; } } catch (NumberFormatException e) { // The number was too long to parse. Fall through to NaN. } } return Double.NaN; } /** * @return Whether the name is an indirect eval. */ private boolean isIndirectEval(Node n) { return n.isName() && "eval".equals(n.getString()) && !n.getBooleanProp(Node.DIRECT_EVAL); } /** * Adds a block or expression, substituting a VOID with an empty statement. * This is used for "for (...);" and "if (...);" type statements. * * @param n The node to print. * @param context The context to determine how the node should be printed. */ private void addNonEmptyStatement( Node n, Context context, boolean allowNonBlockChild) { Node nodeToProcess = n; if (!allowNonBlockChild && !n.isBlock()) { throw new Error("Missing BLOCK child."); } // Strip unneeded blocks, that is blocks with <2 children unless // the CodePrinter specifically wants to keep them. if

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> (n.isBlock()) { int count = getNonEmptyChildCount(n, 2); if (count == 0) { if (cc.shouldPreserveExtraBlocks()) { cc.beginBlock(); cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); } else { cc.endStatement(true); } return; } if (count == 1) { // Hack around a couple of browser bugs: // Safari needs a block around function declarations. // IE6/7 needs a block around DOs. Node firstAndOnlyChild = getFirstNonEmptyChild(n); boolean alwaysWrapInBlock = cc.shouldPreserveExtraBlocks(); if (alwaysWrapInBlock || isOneExactlyFunctionOrDo(firstAndOnlyChild)) { cc.beginBlock(); add(firstAndOnlyChild, Context.STATEMENT); cc.maybeLineBreak(); cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); return; } else { // Continue with the only child. nodeToProcess = firstAndOnlyChild; } } if (count > 1) { context = Context.PRESERVE_BLOCK; } } if (nodeToProcess.isEmpty()) { cc.endStatement(true); } else { add(nodeToProcess, context); // VAR doesn't include ';' since it gets used in expressions - so any // VAR in a statement context needs a call to endStatement() here. if (nodeToProcess.isVar()) { cc.endStatement(); } } } /** * @return Whether the Node is a DO or FUNCTION (with or without * labels). */ private boolean isOneExactlyFunctionOrDo(Node n) { if (n.isLabel()) { Node labeledStatement = n.getLastChild(); if (!labeledStatement.isBlock()) { return isOneExactlyFunctionOrDo(labeledStatement); } else { // For labels with block children, we need to ensure that a // labeled FUNCTION or DO isn't generated when extraneous BLOCKs // are skipped. if (getNonEmptyChildCount(n, 2) == 1) { return isOneExactlyFunctionOrDo(getFirstNonEmptyChild(n)); } else { // Either a empty statement or an block with more than one child, // way it isn't a FUNCTION or DO. return false; } } } else { return (n.isFunction() || n.isDo()); } } private void addExpr(Node n, int minPrecedence, Context context) { if ((NodeUtil.precedence(n.getType()) < minPrecedence) || ((context == Context.IN_FOR_INIT_CLAUSE) && (n.isIn()))){ add("("); add(n, clearContextForNoInOperator(context)); add(")"); } else { add(n, context); } } void addList(Node firstInList) { addList(firstInList, true, Context.OTHER); } void addList(Node firstInList, boolean isArrayOrFunctionArgument) { addList(firstInList

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> sb.append(c); } break; default: // If we're given an outputCharsetEncoder, then check if the // character can be represented in this character set. if (outputCharsetEncoder != null) { if (outputCharsetEncoder.canEncode(c)) { sb.append(c); } else { // Unicode-escape the character. appendHexJavaScriptRepresentation(sb, c); } } else { // No charsetEncoder provided - pass straight Latin characters // through, and escape the rest. Doing the explicit character // check is measurably faster than using the CharsetEncoder. if (c > 0x1f && c < 0x7f) { sb.append(c); } else { // Other characters can be misinterpreted by some JS parsers, // or perhaps mangled by proxies along the way, // so we play it safe and Unicode escape them. appendHexJavaScriptRepresentation(sb, c); } } } } sb.append(quote); return sb.toString(); } static String identifierEscape(String s) { // First check if escaping is needed at all -- in most cases it isn't. if (NodeUtil.isLatin(s)) { return s; } // Now going through the string to escape non-Latin characters if needed. StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); // Identifiers should always go to Latin1/ ASCII characters because // different browser's rules for valid identifier characters are // crazy. if (c > 0x1F && c < 0x7F) { sb.append(c); } else { appendHexJavaScriptRepresentation(sb, c); } } return sb.toString(); } /** * @param maxCount The maximum number of children to look for. * @return The number of children of this node that are non empty up to * maxCount. */ private static int getNonEmptyChildCount(Node n, int maxCount) { int i = 0; Node c = n.getFirstChild(); for (; c != null && i < maxCount; c = c.getNext()) { if (c.isBlock()) { i += getNonEmptyChildCount(c, maxCount-i); } else if (!c.isEmpty()) { i++; } } return i; } /** Gets the first non-empty child of the given node. */ private static Node getFirstNonEmptyChild(Node n) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (c.isBlock()) { Node result = getFirstNonEmptyChild(c); if (result != null) { return result; } } else if (!c.isEmpty()) { return c; } } return null; } // Information on the current context. Used for disambiguating special cases. // For example, a "{" could indicate the start of an object literal or a // block, depending on the current context. enum Context { STATEMENT

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> null) { if (methodName.equals("inherits")) { return SubclassType.INHERITS; } else if (methodName.equals("mixin")) { return SubclassType.MIXIN; } } return null; } @Override public boolean isSuperClassReference(String propertyName) { return "superClass_".equals(propertyName) || super.isSuperClassReference(propertyName); } /** * Given a qualified name node, returns whether "prototype" is at the end. * For example: * a.b.c => false * a.b.c.prototype => true */ private boolean endsWithPrototype(Node qualifiedName) { return qualifiedName.isGetProp() && qualifiedName.getLastChild().getString().equals("prototype"); } /** * Extracts X from goog.provide('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfProvide(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.provide"); } /** * Extracts X from goog.require('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfRequire(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.require"); } private static String extractClassNameIfGoog(Node node, Node parent, String functionName){ String className = null; if (NodeUtil.isExprCall(parent)) { Node callee = node.getFirstChild(); if (callee != null && callee.isGetProp()) { String qualifiedName = callee.getQualifiedName(); if (functionName.equals(qualifiedName)) { Node target = callee.getNext(); if (target != null && target.isString()) { className = target.getString(); } } } } return className; } /** * Use closure's implementation. * @return closure's function name for exporting properties. */ @Override public String getExportPropertyFunction() { return "goog.exportProperty"; } /** * Use closure's implementation. * @return closure's function name for exporting symbols. */ @Override public String getExportSymbolFunction() { return "goog.exportSymbol"; } @Override public List<String> identifyTypeDeclarationCall(Node n) { Node callName = n.getFirstChild(); if ("goog.addDependency".equals(callName.getQualifiedName()) && n.getChildCount() >= 3) { Node typeArray = callName.getNext().getNext(); if (typeArray.isArrayLit()) { List<String> typeNames = Lists.newArrayList(); for (Node name = typeArray.getFirstChild(); name != null; name = name.getNext()) { if (name.isString()) { typeNames.add(name.getString()); } } return typeNames; } } return super.identifyTypeDeclarationCall(n); } @Override public String getAbstractMethodName() { return "goog.abstractMethod"; } @Override public String get

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>: case Token.CATCH: case Token.LABEL: return n != parent.getFirstChild(); case Token.FUNCTION: return n == parent.getFirstChild().getNext().getNext(); case Token.CONTINUE: case Token.BREAK: case Token.EXPR_RESULT: case Token.VAR: case Token.RETURN: case Token.THROW: return false; case Token.TRY: /* Just before we are about to visit the second child of the TRY node, * we know that we will be visiting either the CATCH or the FINALLY. * In other words, we know that the post order traversal of the TRY * block has been finished, no more exceptions can be caught by the * handler at this TRY block and should be taken out of the stack. */ if (n == parent.getFirstChild().getNext()) { Preconditions.checkState(exceptionHandler.peek() == parent); exceptionHandler.pop(); } } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.IF: handleIf(n); return; case Token.WHILE: handleWhile(n); return; case Token.DO: handleDo(n); return; case Token.FOR: handleFor(n); return; case Token.SWITCH: handleSwitch(n); return; case Token.CASE: handleCase(n); return; case Token.DEFAULT_CASE: handleDefault(n); return; case Token.BLOCK: case Token.SCRIPT: handleStmtList(n); return; case Token.FUNCTION: handleFunction(n); return; case Token.EXPR_RESULT: handleExpr(n); return; case Token.THROW: handleThrow(n); return; case Token.TRY: handleTry(n); return; case Token.CATCH: handleCatch(n); return; case Token.BREAK: handleBreak(n); return; case Token.CONTINUE: handleContinue(n); return; case Token.RETURN: handleReturn(n); return; case Token.WITH: handleWith(n); return; case Token.LABEL: return; default: handleStmt(n); return; } } private void handleIf(Node node) { Node thenBlock = node.getFirstChild().getNext(); Node elseBlock = thenBlock.getNext(); createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock)); if (elseBlock == null) { createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); // not taken branch } else { createEdge(node, Branch.ON_FALSE, computeFallThrough(elseBlock)); } connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleWhile(Node node) { // Control goes to the first statement if the condition evaluates to true. createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild().getNext())); // Control goes to the follow() if the

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> condition evaluates to false. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleDo(Node node) { // The first edge can be the initial iteration as well as the iterations // after. createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild())); // The edge that leaves the do loop if the condition fails. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleFor(Node forNode) { if (forNode.getChildCount() == 4) { // We have for (init; cond; iter) { body } Node init = forNode.getFirstChild(); Node cond = init.getNext(); Node iter = cond.getNext(); Node body = iter.getNext(); // After initialization, we transfer to the FOR which is in charge of // checking the condition (for the first time). createEdge(init, Branch.UNCOND, forNode); // The edge that transfer control to the beginning of the loop body. createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body)); // The edge to end of the loop. createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode, this)); // The end of the body will have a unconditional branch to our iter // (handled by calling computeFollowNode of the last instruction of the // body. Our iter will jump to the forNode again to another condition // check. createEdge(iter, Branch.UNCOND, forNode); connectToPossibleExceptionHandler(init, init); connectToPossibleExceptionHandler(forNode, cond); connectToPossibleExceptionHandler(iter, iter); } else { // We have for (item in collection) { body } Node item = forNode.getFirstChild(); Node collection = item.getNext(); Node body = collection.getNext(); // The collection behaves like init. createEdge(collection, Branch.UNCOND, forNode); // The edge that transfer control to the beginning of the loop body. createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body)); // The edge to end of the loop. createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode, this)); connectToPossibleExceptionHandler(forNode, collection); } } private void handleSwitch(Node node) { // Transfer to the first non-DEFAULT CASE. if there are none, transfer // to the DEFAULT or the EMPTY node. Node next = getNextSiblingOfType( node.getFirstChild().getNext(), Token.CASE, Token.EMPTY); if (next != null) { // Has at least one CASE or EMPTY createEdge(node, Branch.UNCOND, next); } else { // Has no CASE but possibly a DEFAULT if (node.getFirstChild().getNext() != null) { createEdge(node, Branch.UNCOND, node.getFirstChild().getNext()); } else { // No CASE, no DEFAULT createEdge(node, Branch.UNCOND

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleCase(Node node) { // Case is a bit tricky....First it goes into the body if condition is true. createEdge(node, Branch.ON_TRUE, node.getFirstChild().getNext()); // Look for the next CASE, skipping over DEFAULT. Node next = getNextSiblingOfType(node.getNext(), Token.CASE); if (next != null) { // Found a CASE Preconditions.checkState(next.isCase()); createEdge(node, Branch.ON_FALSE, next); } else { // No more CASE found, go back and search for a DEFAULT. Node parent = node.getParent(); Node deflt = getNextSiblingOfType( parent.getFirstChild().getNext(), Token.DEFAULT_CASE); if (deflt != null) { // Has a DEFAULT createEdge(node, Branch.ON_FALSE, deflt); } else { // No DEFAULT found, go to the follow of the SWITCH. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleDefault(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleWith(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getLastChild()); connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleStmtList(Node node) { Node parent = node.getParent(); // Special case, don't add a block of empty CATCH block to the graph. if (node.isBlock() && parent != null && parent.isTry() && NodeUtil.getCatchBlock(parent) == node && !NodeUtil.hasCatchHandler(node)) { return; } // A block transfer control to its first child if it is not empty. Node child = node.getFirstChild(); // Function declarations are skipped since control doesn't go into that // function (unless it is called) while (child != null && child.isFunction()) { child = child.getNext(); } if (child != null) { createEdge(node, Branch.UNCOND, computeFallThrough(child)); } else { createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); } // Synthetic blocks if (parent != null) { switch (parent.getType()) { case Token.DEFAULT_CASE: case Token.CASE: case Token.TRY: break; default: if (node.isBlock() && node.isSyntheticBlock()) { createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this)); } break; } } } private void handleFunction(Node node) { // A block transfer control to its first child if it is not empty. Preconditions.checkState(node.getChildCount() >= 3); createEdge(node, Branch

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>.UNCOND, computeFallThrough(node.getFirstChild().getNext().getNext())); Preconditions.checkState(exceptionHandler.peek() == node); exceptionHandler.pop(); } private void handleExpr(Node node) { createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); connectToPossibleExceptionHandler(node, node); } private void handleThrow(Node node) { connectToPossibleExceptionHandler(node, node); } private void handleTry(Node node) { createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleCatch(Node node) { createEdge(node, Branch.UNCOND, node.getLastChild()); } private void handleBreak(Node node) { String label = null; // See if it is a break with label. if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node previous = null; Node lastJump; Node parent = node.getParent(); /* * Continuously look up the ancestor tree for the BREAK target or the target * with the corresponding label and connect to it. If along the path we * discover a FINALLY, we will connect the BREAK to that FINALLY. From then * on, we will just record the control flow changes in the finallyMap. This * is due to the fact that we need to connect any node that leaves its own * FINALLY block to the outer FINALLY or the BREAK's target but those nodes * are not known yet due to the way we traverse the nodes. */ for (cur = node, lastJump = node; !isBreakTarget(cur, label); cur = parent, parent = parent.getParent()) { if (cur.isTry() && NodeUtil.hasFinally(cur) && cur.getLastChild() != previous) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFallThrough( cur.getLastChild())); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } if (parent == null) { if (compiler.isIdeMode()) { // In IDE mode, we expect that the data flow graph may // not be well-formed. return; } else { throw new IllegalStateException("Cannot find break target."); } } previous = cur; } if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this)); } else { finallyMap.put(lastJump, computeFollowNode(cur, this)); } } private void handleContinue(Node node) { String label = null; if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node previous = null; Node lastJump; // Similar to handBreak's logic with a few minor variation. Node parent = node.getParent(); for (cur = node, lastJump = node; !isContinueTarget(cur, parent, label); cur = parent, parent = parent.getParent()) { if (cur.isTry() && NodeUtil.hasFinally(

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>cur) && cur.getLastChild() != previous) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, cur.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find continue target."); previous = cur; } Node iter = cur; if (cur.getChildCount() == 4) { iter = cur.getFirstChild().getNext().getNext(); } if (lastJump == node) { createEdge(node, Branch.UNCOND, iter); } else { finallyMap.put(lastJump, iter); } } private void handleReturn(Node node) { Node lastJump = null; for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) { Node curHandler = iter.next(); if (curHandler.isFunction()) { break; } if (NodeUtil.hasFinally(curHandler)) { if (lastJump == null) { createEdge(node, Branch.UNCOND, curHandler.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(curHandler.getLastChild())); } lastJump = curHandler; } } if (node.hasChildren()) { connectToPossibleExceptionHandler(node, node.getFirstChild()); } if (lastJump == null) { createEdge(node, Branch.UNCOND, null); } else { finallyMap.put(lastJump, null); } } private void handleStmt(Node node) { // Simply transfer to the next line. createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); connectToPossibleExceptionHandler(node, node); } static Node computeFollowNode(Node node, ControlFlowAnalysis cfa) { return computeFollowNode(node, node, cfa); } static Node computeFollowNode(Node node) { return computeFollowNode(node, node, null); } /** * Computes the follow() node of a given node and its parent. There is a side * effect when calling this function. If this function computed an edge that * exists a FINALLY, it'll attempt to connect the fromNode to the outer * FINALLY according to the finallyMap. * * @param fromNode The original source node since {@code node} is changed * during recursion. * @param node The node that follow() should compute. */ private static Node computeFollowNode( Node fromNode, Node node, ControlFlowAnalysis cfa) { /* * This is the case where: * * 1. Parent is null implies that we are transferring control to the end of * the script. * * 2. Parent is a function implies that we are transferring control back to * the caller of the function. * * 3. If the node is a return statement, we should also transfer control * back to the caller of the function. * * 4. If the node is root then we have reached the end of what we have been * asked to traverse.

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> * * In all cases we should transfer control to a "symbolic return" node. * This will make life easier for DFAs. */ Node parent = node.getParent(); if (parent == null || parent.isFunction() || (cfa != null && node == cfa.root)) { return null; } // If we are just before a IF/WHILE/DO/FOR: switch (parent.getType()) { // The follow() of any of the path from IF would be what follows IF. case Token.IF: return computeFollowNode(fromNode, parent, cfa); case Token.CASE: case Token.DEFAULT_CASE: // After the body of a CASE, the control goes to the body of the next // case, without having to go to the case condition. if (parent.getNext() != null) { if (parent.getNext().isCase()) { return parent.getNext().getFirstChild().getNext(); } else if (parent.getNext().isDefaultCase()) { return parent.getNext().getFirstChild(); } else { Preconditions.checkState(false, "Not reachable"); } } else { return computeFollowNode(fromNode, parent, cfa); } break; case Token.FOR: if (NodeUtil.isForIn(parent)) { return parent; } else { return parent.getFirstChild().getNext().getNext(); } case Token.WHILE: case Token.DO: return parent; case Token.TRY: // If we are coming out of the TRY block... if (parent.getFirstChild() == node) { if (NodeUtil.hasFinally(parent)) { // and have FINALLY block. return computeFallThrough(parent.getLastChild()); } else { // and have no FINALLY. return computeFollowNode(fromNode, parent, cfa); } // CATCH block. } else if (NodeUtil.getCatchBlock(parent) == node){ if (NodeUtil.hasFinally(parent)) { // and have FINALLY block. return computeFallThrough(node.getNext()); } else { return computeFollowNode(fromNode, parent, cfa); } // If we are coming out of the FINALLY block... } else if (parent.getLastChild() == node){ if (cfa != null) { for (Node finallyNode : cfa.finallyMap.get(parent)) { cfa.createEdge(fromNode, Branch.UNCOND, finallyNode); } } return computeFollowNode(fromNode, parent, cfa); } } // Now that we are done with the special cases follow should be its // immediate sibling, unless its sibling is a function Node nextSibling = node.getNext(); // Skip function declarations because control doesn't get pass into it. while (nextSibling != null && nextSibling.isFunction()) { nextSibling = nextSibling.getNext(); } if (nextSibling != null) { return computeFallThrough(nextSibling); } else { // If there are no more siblings, control is transferred up the AST. return computeFollowNode(fromNode, parent, cfa); } } /** * Computes the destination node of n when we want

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> to fallthrough into the * subtree of n. We don't always create a CFG edge into n itself because of * DOs and FORs. */ static Node computeFallThrough(Node n) { switch (n.getType()) { case Token.DO: return computeFallThrough(n.getFirstChild()); case Token.FOR: if (NodeUtil.isForIn(n)) { return n.getFirstChild().getNext(); } return computeFallThrough(n.getFirstChild()); case Token.LABEL: return computeFallThrough(n.getLastChild()); default: return n; } } /** * Connects the two nodes in the control flow graph. * * @param fromNode Source. * @param toNode Destination. */ private void createEdge(Node fromNode, ControlFlowGraph.Branch branch, Node toNode) { cfg.createNode(fromNode); cfg.createNode(toNode); cfg.connectIfNotFound(fromNode, branch, toNode); } /** * Connects cfgNode to the proper CATCH block if target subtree might throw * an exception. If there are FINALLY blocks reached before a CATCH, it will * make the corresponding entry in finallyMap. */ private void connectToPossibleExceptionHandler(Node cfgNode, Node target) { if (mayThrowException(target) && !exceptionHandler.isEmpty()) { Node lastJump = cfgNode; for (Node handler : exceptionHandler) { if (handler.isFunction()) { return; } Preconditions.checkState(handler.isTry()); Node catchBlock = NodeUtil.getCatchBlock(handler); if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, handler.getLastChild()); } else { finallyMap.put(lastJump, handler.getLastChild()); } } else { // Has a catch. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, catchBlock); return; } else { finallyMap.put(lastJump, catchBlock); } } lastJump = handler; } } } /** * Get the next sibling (including itself) of one of the given types. */ private static Node getNextSiblingOfType(Node first, int ... types) { for (Node c = first; c != null; c = c.getNext()) { for (int type : types) { if (c.getType() == type) { return c; } } } return null; } /** * Checks if target is actually the break target of labeled continue. The * label can be null if it is an unlabeled break. */ public static boolean isBreakTarget(Node target, String label) { return isBreakStructure(target, label != null) && matchLabel(target.getParent(), label); } /** * Checks if target is actually the continue target of labeled continue. The * label can be null if it is an unlabeled continue. */ private static boolean isContinueTarget( Node target, Node parent, String label) { return isContinueStructure

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>(target) && matchLabel(parent, label); } /** * Check if label is actually referencing the target control structure. If * label is null, it always returns true. */ private static boolean matchLabel(Node target, String label) { if (label == null) { return true; } while (target.isLabel()) { if (target.getFirstChild().getString().equals(label)) { return true; } target = target.getParent(); } return false; } /** * Determines if the subtree might throw an exception. */ public static boolean mayThrowException(Node n) { switch (n.getType()) { case Token.CALL: case Token.GETPROP: case Token.GETELEM: case Token.THROW: case Token.NEW: case Token.ASSIGN: case Token.INC: case Token.DEC: case Token.INSTANCEOF: return true; case Token.FUNCTION: return false; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) { return true; } } return false; } /** * Determines whether the given node can be terminated with a BREAK node. */ static boolean isBreakStructure(Node n, boolean labeled) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.SWITCH: return true; case Token.BLOCK: case Token.IF: case Token.TRY: return labeled; default: return false; } } /** * Determines whether the given node can be advanced with a CONTINUE node. */ static boolean isContinueStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * Get the TRY block with a CATCH that would be run if n throws an exception. * @return The CATCH node or null if it there isn't a CATCH before the * the function terminates. */ static Node getExceptionHandler(Node n) { for (Node cur = n; !cur.isScript() && !cur.isFunction(); cur = cur.getParent()) { Node catchNode = getCatchHandlerForBlock(cur); if (catchNode != null) { return catchNode; } } return null; } /** * Locate the catch BLOCK given the first block in a TRY. * @return The CATCH node or null there is no catch handler. */ static Node getCatchHandlerForBlock(Node block) { if (block.isBlock() && block.getParent().isTry() && block.getParent().getFirstChild() == block) { for (Node s = block.getNext(); s != null; s = s.getNext()) { if (NodeUtil.hasCatchHandler(s)) { return s.getFirstChild(); } } } return null; } /** * A {@link ControlFlowGraph} which provides

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> (in particular, if not overridden) */ public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator( boolean isForward) { return null; } /** * The edge object for the control flow graph. */ public static enum Branch { /** Edge is taken if the condition is true. */ ON_TRUE, /** Edge is taken if the condition is false. */ ON_FALSE, /** Unconditional branch. */ UNCOND, /** Exception related. */ ON_EX, /** Possible folded-away template */ SYN_BLOCK; public boolean isConditional() { return this == ON_TRUE || this == ON_FALSE; } } /** * Abstract callback to visit a control flow graph node without going into * subtrees of the node that is also represented by another control flow graph * node. * * <p>For example, traversing an IF node as root will visit the two subtree * pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and * {@link ControlFlowGraph.Branch#ON_FALSE} edge. */ public abstract static class AbstractCfgNodeTraversalCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (parent == null) { return true; } return !isEnteringNewCfgNode(n); } } /** * @return True if n should be represented by a new CFG node in the control * flow graph. */ public static boolean isEnteringNewCfgNode(Node n) { Node parent = n.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.SCRIPT: case Token.TRY: return true; case Token.FUNCTION: // A function node represents the start of a function where the name // is bleed into the local scope and parameters has been assigned // to the formal argument names. The node includes the name of the // function and the LP list since we assume the whole set up process // is atomic without change in control flow. The next change of // control is going into the function's body represent by the second // child. return n != parent.getFirstChild().getNext(); case Token.WHILE: case Token.DO: case Token.IF: // Theses control structure is represented by its node that holds the // condition. Each of them is a branch node based on its condition. return NodeUtil.getConditionExpression(parent) != n; case Token.FOR: // The FOR(;;) node differs from other control structure in that // it has a initialization and a increment statement. Those // two statements have its corresponding CFG nodes to represent them. // The FOR node represents the condition check for each iteration. // That way the following: // for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to // var x = 0; while(x<10) { x++; } if (NodeUtil.isForIn(parent)) { // TODO(user): Investigate how we should handle the case where // we have a very complex expression inside the FOR-IN header. return n != parent

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>.getFirstChild(); } else { return NodeUtil.getConditionExpression(parent) != n; } case Token.SWITCH: case Token.CASE: case Token.CATCH: case Token.WITH: return n != parent.getFirstChild(); default: return false; } } }

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>) { // Constructing the global scope is very different than constructing // inner scopes, because only global scopes can contain named classes that // show up in the type registry. Scope newScope = null; AbstractScopeBuilder scopeBuilder = null; if (parent == null) { // Run a first-order analysis over the syntax tree. (new FirstOrderFunctionAnalyzer(compiler, functionAnalysisResults)) .process(root.getFirstChild(), root.getLastChild()); // Find all the classes in the global scope. newScope = createInitialScope(root); GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope); scopeBuilder = globalScopeBuilder; NodeTraversal.traverse(compiler, root, scopeBuilder); } else { newScope = new Scope(parent, root); LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope); scopeBuilder = localScopeBuilder; localScopeBuilder.build(); } scopeBuilder.resolveStubDeclarations(); scopeBuilder.resolveTypes(); // Gather the properties in each function that we found in the // global scope, if that function has a @this type that we can // build properties on. for (Node functionNode : scopeBuilder.nonExternFunctions) { JSType type = functionNode.getJSType(); if (type != null && type.isFunctionType()) { FunctionType fnType = type.toMaybeFunctionType(); ObjectType fnThisType = fnType.getTypeOfThis(); if (!fnThisType.isUnknownType()) { NodeTraversal.traverse(compiler, functionNode.getLastChild(), scopeBuilder.new CollectProperties(fnThisType)); } } } if (parent == null) { codingConvention.defineDelegateProxyPrototypeProperties( typeRegistry, newScope, delegateProxyPrototypes, delegateCallingConventions); } return newScope; } /** * Patches a given global scope by removing variables previously declared in * a script and re-traversing a new version of that script. * * @param globalScope The global scope generated by {@code createScope}. * @param scriptRoot The script that is modified. */ void patchGlobalScope(Scope globalScope, Node scriptRoot) { // Preconditions: This is supposed to be called only on (named) SCRIPT nodes // and a global typed scope should have been generated already. Preconditions.checkState(scriptRoot.isScript()); Preconditions.checkNotNull(globalScope); Preconditions.checkState(globalScope.isGlobal()); String scriptName = NodeUtil.getSourceName(scriptRoot); Preconditions.checkNotNull(scriptName); for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) { if (scriptName.equals(NodeUtil.getSourceName(node))) { functionAnalysisResults.remove(node); } } (new FirstOrderFunctionAnalyzer( compiler, functionAnalysisResults)).process(null, scriptRoot); // TODO(bashir): Variable declaration is not the only side effect of last // global scope generation but here we only wipe that part off! // Remove all variables that were previously declared in this scripts. // First find all vars to remove then remove them because of iterator! Iterator<Var> varIter = globalScope.getVars(); List<Var> varsToRemove

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>node.getType()) { case Token.VAR: for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { identifyNameNode( child, child.getFirstChild(), NodeUtil.getBestJSDocInfo(child)); } break; case Token.EXPR_RESULT: Node firstChild = node.getFirstChild(); if (firstChild.isAssign()) { identifyNameNode( firstChild.getFirstChild(), firstChild.getLastChild(), firstChild.getJSDocInfo()); } else { identifyNameNode( firstChild, null, firstChild.getJSDocInfo()); } break; } } private void identifyNameNode( Node nameNode, Node valueNode, JSDocInfo info) { if (nameNode.isQualifiedName()) { if (info != null) { if (info.hasEnumParameterType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } else if (info.hasTypedefType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } } } } } private JSType getNativeType(JSTypeNative nativeType) { return typeRegistry.getNativeType(nativeType); } private abstract class AbstractScopeBuilder implements NodeTraversal.Callback { /** * The scope that we're building. */ final Scope scope; private final List<DeferredSetType> deferredSetTypes = Lists.newArrayList(); /** * Functions that we found in the global scope and not in externs. */ private final List<Node> nonExternFunctions = Lists.newArrayList(); /** * Object literals with a @lends annotation aren't analyzed until we * reach the root of the statement they're defined in. * * This ensures that if there are any @lends annotations on the object * literals, the type on the @lends annotation resolves correctly. * * For more information, see * http://code.google.com/p/closure-compiler/issues/detail?id=314 */ private List<Node> lentObjectLiterals = null; /** * Type-less stubs. * * If at the end of traversal, we still don't have types for these * stubs, then we should declare UNKNOWN types. */ private final List<StubDeclaration> stubDeclarations = Lists.newArrayList(); /** * The current source file that we're in. */ private String sourceName = null; /** * The InputId of the current node. */ private InputId inputId; private AbstractScopeBuilder(Scope scope) { this.scope = scope; } void setDeferredType(Node node, JSType type) { deferredSetTypes.add(new DeferredSetType(node, type)); } void resolveTypes() { // Resolve types and attach them to nodes. for (DeferredSetType deferred : deferredSetTypes) { deferred.resolve(scope); } // Resolve types and attach them to scope slots. Iterator<Var> vars = scope.getVars(); while (vars.hasNext()) { vars.next().resolveType(typeParsingErrorReporter); } // Tell the type registry that any

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> remaining types // are unknown. typeRegistry.resolveTypesInScope(scope); } @Override public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); if (n.isFunction() || n.isScript()) { Preconditions.checkNotNull(inputId); sourceName = NodeUtil.getSourceName(n); } // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. boolean descend = parent == null || !parent.isFunction() || n == parent.getFirstChild() || parent == scope.getRootNode(); if (descend) { // Handle hoisted functions on pre-order traversal, so that they // get hit before other things in the scope. if (NodeUtil.isStatementParent(n)) { for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (NodeUtil.isHoistedFunctionDeclaration(child)) { defineFunctionLiteral(child, n); } } } } return descend; } @Override public void visit(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); attachLiteralTypes(t, n); switch (n.getType()) { case Token.CALL: checkForClassDefiningCalls(t, n, parent); checkForCallingConventionDefiningCalls(n, delegateCallingConventions); break; case Token.FUNCTION: if (t.getInput() == null || !t.getInput().isExtern()) { nonExternFunctions.add(n); } // Hoisted functions are handled during pre-traversal. if (!NodeUtil.isHoistedFunctionDeclaration(n)) { defineFunctionLiteral(n, parent); } break; case Token.ASSIGN: // Handle initialization of properties. Node firstChild = n.getFirstChild(); if (firstChild.isGetProp() && firstChild.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), firstChild, n, firstChild.getNext()); } break; case Token.CATCH: defineCatch(n, parent); break; case Token.VAR: defineVar(n, parent); break; case Token.GETPROP: // Handle stubbed properties. if (parent.isExprResult() && n.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null); } break; } // Analyze any @lends object literals in this statement. if (n.getParent() != null && NodeUtil.isStatement(n) && lentObjectLiterals != null) { for (Node objLit : lentObjectLiterals) { defineObjectLiteral(objLit); } lentObjectLiterals.clear(); } } private void attachLiteralTypes(NodeTraversal t, Node n) { switch (n.getType()) { case Token.NULL: n.setJSType(getNativeType(NULL_TYPE)); break; case Token.VOID: n.setJSType(getNativeType(

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>VOID_TYPE)); break; case Token.STRING: n.setJSType(getNativeType(STRING_TYPE)); break; case Token.NUMBER: n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.TRUE: case Token.FALSE: n.setJSType(getNativeType(BOOLEAN_TYPE)); break; case Token.REGEXP: n.setJSType(getNativeType(REGEXP_TYPE)); break; case Token.OBJECTLIT: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.getLendsName() != null) { if (lentObjectLiterals == null) { lentObjectLiterals = Lists.newArrayList(); } lentObjectLiterals.add(n); } else { defineObjectLiteral(n); } break; // NOTE(nicksantos): If we ever support Array tuples, // we will need to put ARRAYLIT here as well. } } private void defineObjectLiteral(Node objectLit) { // Handle the @lends annotation. JSType type = null; JSDocInfo info = objectLit.getJSDocInfo(); if (info != null && info.getLendsName() != null) { String lendsName = info.getLendsName(); Var lendsVar = scope.getVar(lendsName); if (lendsVar == null) { compiler.report( JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName)); } else { type = lendsVar.getType(); if (type == null) { type = typeRegistry.getNativeType(UNKNOWN_TYPE); } if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) { compiler.report( JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT, lendsName, type.toString())); type = null; } else { objectLit.setJSType(type); } } } info = NodeUtil.getBestJSDocInfo(objectLit); Node lValue = NodeUtil.getBestLValue(objectLit); String lValueName = NodeUtil.getBestLValueName(lValue); boolean createdEnumType = false; if (info != null && info.hasEnumParameterType()) { type = createEnumTypeFromNodes(objectLit, lValueName, info, lValue); createdEnumType = true; } if (type == null) { type = typeRegistry.createAnonymousObjectType(); } setDeferredType(objectLit, type); // If this is an enum, the properties were already taken care of above. processObjectLitProperties( objectLit, ObjectType.cast(objectLit.getJSType()), !createdEnumType); } /** * Process an object literal and all the types on it. * @param objLit The OBJECTLIT node. * @param objLitType The type of the OBJECTLIT node. This might be a named * type, because of the lends annotation. * @param declareOnOwner If true, declare properties

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> on the objLitType as * well. If false, the caller should take care of this. */ void processObjectLitProperties( Node objLit, ObjectType objLitType, boolean declareOnOwner) { for (Node keyNode = objLit.getFirstChild(); keyNode != null; keyNode = keyNode.getNext()) { Node value = keyNode.getFirstChild(); String memberName = NodeUtil.getObjectLitKeyName(keyNode); JSDocInfo info = keyNode.getJSDocInfo(); JSType valueType = getDeclaredType(keyNode.getSourceFileName(), info, keyNode, value); JSType keyType = objLitType.isEnumType() ? objLitType.toMaybeEnumType().getElementsType() : NodeUtil.getObjectLitKeyTypeFromValueType(keyNode, valueType); // Try to declare this property in the current scope if it // has an authoritative name. String qualifiedName = NodeUtil.getBestLValueName(keyNode); if (qualifiedName != null) { boolean inferred = keyType == null; defineSlot(keyNode, objLit, qualifiedName, keyType, inferred); } else if (keyType != null) { setDeferredType(keyNode, keyType); } if (keyType != null && objLitType != null && declareOnOwner) { // Declare this property on its object literal. boolean isExtern = keyNode.isFromExterns(); objLitType.defineDeclaredProperty(memberName, keyType, keyNode); } } } /** * Returns the type specified in a JSDoc annotation near a GETPROP or NAME. * * Extracts type information from either the {@code @type} tag or from * the {@code @return} and {@code @param} tags. */ private JSType getDeclaredTypeInAnnotation(String sourceName, Node node, JSDocInfo info) { JSType jsType = null; Node objNode = node.isGetProp() ? node.getFirstChild() : NodeUtil.isObjectLitKey(node, node.getParent()) ? node.getParent() : null; if (info != null) { if (info.hasType()) { jsType = info.getType().evaluate(scope, typeRegistry); } else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) { String fnName = node.getQualifiedName(); jsType = createFunctionTypeFromNodes( null, fnName, info, node); } } return jsType; } /** * Asserts that it's OK to define this node's name. * The node should have a source name and be of the specified type. */ void assertDefinitionNode(Node n, int type) { Preconditions.checkState(sourceName != null); Preconditions.checkState(n.getType() == type); } /** * Defines a catch parameter. */ void defineCatch(Node n, Node parent) { assertDefinitionNode(n, Token.CATCH); Node catchName = n.getFirstChild(); defineSlot(catchName, n, null); } /** * Defines a VAR initialization. */ void defineVar(Node n, Node parent) { assert

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>DefinitionNode(n, Token.VAR); JSDocInfo info = n.getJSDocInfo(); if (n.hasMoreThanOneChild()) { if (info != null) { // multiple children compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF)); } for (Node name : n.children()) { defineName(name, n, parent, name.getJSDocInfo()); } } else { Node name = n.getFirstChild(); defineName(name, n, parent, (info != null) ? info : name.getJSDocInfo()); } } /** * Defines a function literal. */ void defineFunctionLiteral(Node n, Node parent) { assertDefinitionNode(n, Token.FUNCTION); // Determine the name and JSDocInfo and l-value for the function. // Any of these may be null. Node lValue = NodeUtil.getBestLValue(n); JSDocInfo info = NodeUtil.getBestJSDocInfo(n); String functionName = NodeUtil.getBestLValueName(lValue); FunctionType functionType = createFunctionTypeFromNodes(n, functionName, info, lValue); // Assigning the function type to the function node setDeferredType(n, functionType); // Declare this symbol in the current scope iff it's a function // declaration. Otherwise, the declaration will happen in other // code paths. if (NodeUtil.isFunctionDeclaration(n)) { defineSlot(n.getFirstChild(), n, functionType); } } /** * Defines a variable based on the {@link Token#NAME} node passed. * @param name The {@link Token#NAME} node. * @param var The parent of the {@code name} node, which must be a * {@link Token#VAR} node. * @param parent {@code var}'s parent. * @param info the {@link JSDocInfo} information relating to this * {@code name} node. */ private void defineName(Node name, Node var, Node parent, JSDocInfo info) { Node value = name.getFirstChild(); // variable's type JSType type = getDeclaredType(sourceName, info, name, value); if (type == null) { // The variable's type will be inferred. type = name.isFromExterns() ? getNativeType(UNKNOWN_TYPE) : null; } defineSlot(name, var, type); } /** * If a variable is assigned a function literal in the global scope, * make that a declared type (even if there's no doc info). * There's only one exception to this rule: * if the return type is inferred, and we're in a local * scope, we should assume the whole function is inferred. */ private boolean shouldUseFunctionLiteralType( FunctionType type, JSDocInfo info, Node lValue) { if (info != null) { return true; } if (lValue != null && NodeUtil.isObjectLitKey(lValue, lValue.getParent())) { return false; } return scope.isGlobal() ||

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS> !type.isReturnTypeInferred(); } /** * Creates a new function type, based on the given nodes. * * This handles two cases that are semantically very different, but * are not mutually exclusive: * - A function literal that needs a type attached to it. * - An assignment expression with function-type info in the JsDoc. * * All parameters are optional, and we will do the best we can to create * a function type. * * This function will always create a function type, so only call it if * you're sure that's what you want. * * @param rValue The function node. * @param name the function's name * @param info the {@link JSDocInfo} attached to the function definition * @param lvalueNode The node where this function is being * assigned. For example, {@code A.prototype.foo = ...} would be used to * determine that this function is a method of A.prototype. May be * null to indicate that this is not being assigned to a qualified name. */ private FunctionType createFunctionTypeFromNodes( @Nullable Node rValue, @Nullable String name, @Nullable JSDocInfo info, @Nullable Node lvalueNode) { FunctionType functionType = null; // Global ctor aliases should be registered with the type registry. if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) { Var var = scope.getVar(rValue.getQualifiedName()); if (var != null && var.getType() != null && var.getType().isFunctionType()) { FunctionType aliasedType = var.getType().toMaybeFunctionType(); if ((aliasedType.isConstructor() || aliasedType.isInterface()) && !aliasedType.isNativeObjectType()) { functionType = aliasedType; if (name != null && scope.isGlobal()) { typeRegistry.declareType(name, functionType.getInstanceType()); } } } } if (functionType == null) { Node errorRoot = rValue == null ? lvalueNode : rValue; boolean isFnLiteral = rValue != null && rValue.isFunction(); Node fnRoot = isFnLiteral ? rValue : null; Node parametersNode = isFnLiteral ? rValue.getFirstChild().getNext() : null; Node fnBlock = isFnLiteral ? parametersNode.getNext() : null; if (info != null && info.hasType()) { JSType type = info.getType().evaluate(scope, typeRegistry); // Known to be not null since we have the FUNCTION token there. type = type.restrictByNotNullOrUndefined(); if (type.isFunctionType()) { functionType = type.toMaybeFunctionType(); functionType.setJSDocInfo(info); } } if (functionType == null) { // Find the type of any overridden function. Node ownerNode = NodeUtil.getBestLValueOwner(lvalueNode); String ownerName = NodeUtil.getBestLValueName(ownerNode); Var ownerVar = null; String propName = null; ObjectType ownerType = null; if (ownerName != null) { ownerVar = scope.

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>.hasEnumParameterType()); EnumType enumType = null; if (rValue != null && rValue.isQualifiedName()) { // Handle an aliased enum. Var var = scope.getVar(rValue.getQualifiedName()); if (var != null && var.getType() instanceof EnumType) { enumType = (EnumType) var.getType(); } } if (enumType == null) { JSType elementsType = info.getEnumParameterType().evaluate(scope, typeRegistry); enumType = typeRegistry.createEnumType(name, rValue, elementsType); if (rValue != null && rValue.isObjectLit()) { // collect enum elements Node key = rValue.getFirstChild(); while (key != null) { String keyName = NodeUtil.getStringValue(key); if (keyName == null) { // GET and SET don't have a String value; compiler.report( JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName)); } else if (!codingConvention.isValidEnumKey(keyName)) { compiler.report( JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName)); } else { enumType.defineElement(keyName, key); } key = key.getNext(); } } } if (name != null && scope.isGlobal()) { typeRegistry.declareType(name, enumType.getElementsType()); } return enumType; } /** * Defines a typed variable. The defining node will be annotated with the * variable's type or {@code null} if its type is inferred. * @param name the defining node. It must be a {@link Token#NAME}. * @param parent the {@code name}'s parent. * @param type the variable's type. It may be {@code null}, in which case * the variable's type will be inferred. */ private void defineSlot(Node name, Node parent, JSType type) { defineSlot(name, parent, type, type == null); } /** * Defines a typed variable. The defining node will be annotated with the * variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is * inferred. * * Slots may be any variable or any qualified name in the global scope. * * @param n the defining NAME or GETPROP node. * @param parent the {@code n}'s parent. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. */ void defineSlot(Node n, Node parent, JSType type, boolean inferred) { Preconditions.checkArgument(inferred || type != null); // Only allow declarations of NAMEs and qualified names. // Object literal keys will have to compute their names themselves. if (n.isName()) { Preconditions.checkArgument( parent.isFunction() || parent.isVar() || parent.isParamList() || parent.isCatch()); } else { Preconditions.checkArgument( n.isGetProp() && (parent.isAssign() || parent.isExprResult())); } defineSlot(n,

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>"); // When we declare the function prototype implicitly, we // want to make sure that the function and its prototype // are declared at the same node. We also want to make sure // that the if a symbol has both a Var and a JSType, they have // the same node. // // This consistency is helpful to users of SymbolTable, // because everything gets declared at the same place. prototypeSlot.setNode(n); String prototypeName = variableName + ".prototype"; // There are some rare cases where the prototype will already // be declared. See TypedScopeCreatorTest#testBogusPrototypeInit. // Fortunately, other warnings will complain if this happens. Var prototypeVar = scopeToDeclareIn.getVar(prototypeName); if (prototypeVar != null && prototypeVar.scope == scopeToDeclareIn) { scopeToDeclareIn.undeclare(prototypeVar); } scopeToDeclareIn.declare(prototypeName, n, prototypeSlot.getType(), input, /* declared iff there's an explicit supertype */ superClassCtor == null || superClassCtor.getInstanceType().equals( getNativeType(OBJECT_TYPE))); // Make sure the variable is initialized to something if // it constructs itself. if (newVar.getInitialValue() == null && !isExtern && // We want to make sure that when we declare a new instance // type (with @constructor) that there's actually a ctor for it. // This doesn't apply to structural constructors // (like function(new:Array). Checking the constructed // type against the variable name is a sufficient check for // this. variableName.equals( fnType.getInstanceType().getReferenceName())) { compiler.report( JSError.make(sourceName, n, fnType.isConstructor() ? CTOR_INITIALIZER : IFACE_INITIALIZER, variableName)); } } } if (shouldDeclareOnGlobalThis) { ObjectType globalThis = typeRegistry.getNativeObjectType(GLOBAL_THIS); if (inferred) { globalThis.defineInferredProperty(variableName, type == null ? getNativeType(JSTypeNative.NO_TYPE) : type, n); } else { globalThis.defineDeclaredProperty(variableName, type, n); } } if (isGlobalVar && "Window".equals(variableName) && type != null && type.isFunctionType() && type.isConstructor()) { FunctionType globalThisCtor = typeRegistry.getNativeObjectType(GLOBAL_THIS).getConstructor(); globalThisCtor.getInstanceType().clearCachedValues(); globalThisCtor.getPrototype().clearCachedValues(); globalThisCtor .setPrototypeBasedOn((type.toMaybeFunctionType()).getInstanceType()); } } /** * Check if the given node is a property of a name in the global scope. */ private boolean isQnameRootedInGlobalScope(Node n) { Scope scope = getQnameRootScope(n); return scope != null && scope.isGlobal(); } /** * Return the scope for the name of the given node. */ private Scope getQnameRootScope(Node n) { Node root = NodeUtil.getRootOfQualifiedName(

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>oxious. * * The problem is that there are two (equally valid) coding styles: * * (function() { * /* The authoritative definition of goog.bar. / * goog.bar = function() {}; * })(); * * function f() { * goog.bar(); * /* Reset goog.bar to a no-op. / * goog.bar = function() {}; * } * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (info.isConstant() && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; } else if (!scope.isDeclared(qName, false) && n.isUnscopedQualifiedName()) { // Check if this is in a conditional block. // Functions assigned in conditional blocks are inferred. for (Node current = n.getParent(); !(current.isScript() || current.isFunction()); current = current.getParent()) { if (NodeUtil.isControlStructure(current)) { return true; } } // Check if this is assigned in an inner scope. // Functions assigned in inner scopes are inferred. AstFunctionContents contents = getFunctionAnalysisResults(scope.getRootNode()); if (contents == null || !contents.getEscapedQualifiedNames().contains(qName)) { return false; } } } return inferred; } /** * Find the ObjectType associated with the given slot. * @param slotName The name of the slot to find the type in. * @return An object type, or null if this slot does not contain an object. */ private ObjectType getObjectSlot(String slotName) { Var ownerVar = scope.getVar(slotName); if (ownerVar != null) { JSType ownerVarType = ownerVar.getType(); return ObjectType.cast(ownerVarType == null ? null : ownerVarType.restrictByNotNullOrUndefined()); } return null; } /** * Resolve any stub declarations to unknown types if we could not * find types for them during traversal. */ void resolveStubDeclarations() { for (StubDeclaration stub : stubDeclarations) { Node n = stub.node; Node parent = n.getParent(); String qName = n.getQualifiedName(); String propName = n.

Closure, 20

<FILEB>
<CHANGES>
if (value!= null && value.getNext() == null &&
NodeUtil.isImmutableValue(value)) {
<CHANGEE>
<FILEE>
<FILEB> replacement.addChildToBack(fixedIfCondition); } reportCodeChange(); } } } private Node tryFoldSimpleFunctionCall(Node n) { Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); if (callTarget != null && callTarget.isName() && callTarget.getString().equals("String")) { // Fold String(a) to '' + (a) on immutable literals, // which allows further optimizations // // We can't do this in the general case, because String(a) has // slightly different semantics than '' + (a). See // http://code.google.com/p/closure-compiler/issues/detail?id=759 Node value = callTarget.getNext(); <CHANGES> if (value != null) { <CHANGEE> Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable <FILEE> <SCANS>Traversal t, Node n, Node parent) { if (t.inGlobalScope()) { return; } if (n.isReturn() && n.getFirstChild() != null) { data.get(t.getScopeRoot()).recordNonEmptyReturn(); } if (t.getScopeDepth() <= 2) { // We only need to worry about escaped variables at depth 3. // An variable escaped at depth 2 is, by definition, a global variable. // We treat all global variables as escaped by default, so there's // no reason to do this extra computation for them. return; } if (n.isName() && NodeUtil.isLValue(n)) { String name = n.getString(); Scope scope = t.getScope(); Var var = scope.getVar(name); if (var != null) { Scope ownerScope = var.getScope(); if (scope != ownerScope && ownerScope.isLocal()) { data.get(ownerScope.getRootNode()).recordEscapedVarName(name); } } } else if (n.isGetProp() && n.isUnscopedQualifiedName() && NodeUtil.isLValue(n)) { String name = NodeUtil.getRootOfQualifiedName(n).getString(); Scope scope = t.getScope(); Var var = scope.getVar(name); if (var != null) { Scope ownerScope = var.getScope(); if (scope != ownerScope && ownerScope.isLocal()) { data.get(ownerScope.getRootNode()) .recordEscapedQualifiedName(n.getQualifiedName()); } } } } } private AstFunctionContents getFunctionAnalysisResults(@Nullable Node n) { if (n == null) { return null; } // Sometimes this will return null in things like // NameReferenceGraphConstruction that build partial scopes. return functionAnalysisResults.get(n); } }